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 all disks with the VMID from all enabled storages.",
1524 default => 1, # FIXME: replace to false in PVE 7.0, this is dangerous!
1534 my $rpcenv = PVE
::RPCEnvironment
::get
();
1535 my $authuser = $rpcenv->get_user();
1536 my $vmid = $param->{vmid
};
1538 my $skiplock = $param->{skiplock
};
1539 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1540 if $skiplock && $authuser ne 'root@pam';
1542 my $early_checks = sub {
1544 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1545 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1547 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1549 if (!$param->{purge
}) {
1550 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1552 # don't allow destroy if with replication jobs but no purge param
1553 my $repl_conf = PVE
::ReplicationConfig-
>new();
1554 $repl_conf->check_for_existing_jobs($vmid);
1557 die "VM $vmid is running - destroy failed\n"
1558 if PVE
::QemuServer
::check_running
($vmid);
1568 my $storecfg = PVE
::Storage
::config
();
1570 syslog
('info', "destroy VM $vmid: $upid\n");
1571 PVE
::QemuConfig-
>lock_config($vmid, sub {
1572 # repeat, config might have changed
1573 my $ha_managed = $early_checks->();
1575 # FIXME: drop fallback to true with 7.0, to dangerous for default
1576 my $purge_unreferenced = $param->{'destroy-unreferenced-disks'} // 1;
1578 PVE
::QemuServer
::destroy_vm
(
1581 $skiplock, { lock => 'destroyed' },
1582 $purge_unreferenced,
1585 PVE
::AccessControl
::remove_vm_access
($vmid);
1586 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1587 if ($param->{purge
}) {
1588 print "purging VM $vmid from related configurations..\n";
1589 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1590 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1593 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1594 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1598 # only now remove the zombie config, else we can have reuse race
1599 PVE
::QemuConfig-
>destroy_config($vmid);
1603 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1606 __PACKAGE__-
>register_method({
1608 path
=> '{vmid}/unlink',
1612 description
=> "Unlink/delete disk images.",
1614 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1617 additionalProperties
=> 0,
1619 node
=> get_standard_option
('pve-node'),
1620 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1622 type
=> 'string', format
=> 'pve-configid-list',
1623 description
=> "A list of disk IDs you want to delete.",
1627 description
=> $opt_force_description,
1632 returns
=> { type
=> 'null'},
1636 $param->{delete} = extract_param
($param, 'idlist');
1638 __PACKAGE__-
>update_vm($param);
1643 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1644 my $gen_rand_chars = sub {
1647 die "invalid length $length" if $length < 1;
1649 my $min = ord('!'); # first printable ascii
1651 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1652 die "failed to generate random bytes!\n"
1655 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1662 __PACKAGE__-
>register_method({
1664 path
=> '{vmid}/vncproxy',
1668 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1670 description
=> "Creates a TCP VNC proxy connections.",
1672 additionalProperties
=> 0,
1674 node
=> get_standard_option
('pve-node'),
1675 vmid
=> get_standard_option
('pve-vmid'),
1679 description
=> "starts websockify instead of vncproxy",
1681 'generate-password' => {
1685 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1690 additionalProperties
=> 0,
1692 user
=> { type
=> 'string' },
1693 ticket
=> { type
=> 'string' },
1696 description
=> "Returned if requested with 'generate-password' param."
1697 ." Consists of printable ASCII characters ('!' .. '~').",
1700 cert
=> { type
=> 'string' },
1701 port
=> { type
=> 'integer' },
1702 upid
=> { type
=> 'string' },
1708 my $rpcenv = PVE
::RPCEnvironment
::get
();
1710 my $authuser = $rpcenv->get_user();
1712 my $vmid = $param->{vmid
};
1713 my $node = $param->{node
};
1714 my $websocket = $param->{websocket
};
1716 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1720 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1721 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1724 my $authpath = "/vms/$vmid";
1726 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1727 my $password = $ticket;
1728 if ($param->{'generate-password'}) {
1729 $password = $gen_rand_chars->(8);
1732 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1738 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1739 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1740 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1741 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1742 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1744 $family = PVE
::Tools
::get_host_address_family
($node);
1747 my $port = PVE
::Tools
::next_vnc_port
($family);
1754 syslog
('info', "starting vnc proxy $upid\n");
1758 if (defined($serial)) {
1760 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1762 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1763 '-timeout', $timeout, '-authpath', $authpath,
1764 '-perm', 'Sys.Console'];
1766 if ($param->{websocket
}) {
1767 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1768 push @$cmd, '-notls', '-listen', 'localhost';
1771 push @$cmd, '-c', @$remcmd, @$termcmd;
1773 PVE
::Tools
::run_command
($cmd);
1777 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1779 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1781 my $sock = IO
::Socket
::IP-
>new(
1786 GetAddrInfoFlags
=> 0,
1787 ) or die "failed to create socket: $!\n";
1788 # Inside the worker we shouldn't have any previous alarms
1789 # running anyway...:
1791 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1793 accept(my $cli, $sock) or die "connection failed: $!\n";
1796 if (PVE
::Tools
::run_command
($cmd,
1797 output
=> '>&'.fileno($cli),
1798 input
=> '<&'.fileno($cli),
1801 die "Failed to run vncproxy.\n";
1808 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1810 PVE
::Tools
::wait_for_vnc_port
($port);
1819 $res->{password
} = $password if $param->{'generate-password'};
1824 __PACKAGE__-
>register_method({
1825 name
=> 'termproxy',
1826 path
=> '{vmid}/termproxy',
1830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1832 description
=> "Creates a TCP proxy connections.",
1834 additionalProperties
=> 0,
1836 node
=> get_standard_option
('pve-node'),
1837 vmid
=> get_standard_option
('pve-vmid'),
1841 enum
=> [qw(serial0 serial1 serial2 serial3)],
1842 description
=> "opens a serial terminal (defaults to display)",
1847 additionalProperties
=> 0,
1849 user
=> { type
=> 'string' },
1850 ticket
=> { type
=> 'string' },
1851 port
=> { type
=> 'integer' },
1852 upid
=> { type
=> 'string' },
1858 my $rpcenv = PVE
::RPCEnvironment
::get
();
1860 my $authuser = $rpcenv->get_user();
1862 my $vmid = $param->{vmid
};
1863 my $node = $param->{node
};
1864 my $serial = $param->{serial
};
1866 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1868 if (!defined($serial)) {
1870 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1871 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1875 my $authpath = "/vms/$vmid";
1877 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1882 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1883 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1884 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1885 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1886 push @$remcmd, '--';
1888 $family = PVE
::Tools
::get_host_address_family
($node);
1891 my $port = PVE
::Tools
::next_vnc_port
($family);
1893 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1894 push @$termcmd, '-iface', $serial if $serial;
1899 syslog
('info', "starting qemu termproxy $upid\n");
1901 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1902 '--perm', 'VM.Console', '--'];
1903 push @$cmd, @$remcmd, @$termcmd;
1905 PVE
::Tools
::run_command
($cmd);
1908 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1910 PVE
::Tools
::wait_for_vnc_port
($port);
1920 __PACKAGE__-
>register_method({
1921 name
=> 'vncwebsocket',
1922 path
=> '{vmid}/vncwebsocket',
1925 description
=> "You also need to pass a valid ticket (vncticket).",
1926 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1928 description
=> "Opens a weksocket for VNC traffic.",
1930 additionalProperties
=> 0,
1932 node
=> get_standard_option
('pve-node'),
1933 vmid
=> get_standard_option
('pve-vmid'),
1935 description
=> "Ticket from previous call to vncproxy.",
1940 description
=> "Port number returned by previous vncproxy call.",
1950 port
=> { type
=> 'string' },
1956 my $rpcenv = PVE
::RPCEnvironment
::get
();
1958 my $authuser = $rpcenv->get_user();
1960 my $vmid = $param->{vmid
};
1961 my $node = $param->{node
};
1963 my $authpath = "/vms/$vmid";
1965 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1967 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1969 # Note: VNC ports are acessible from outside, so we do not gain any
1970 # security if we verify that $param->{port} belongs to VM $vmid. This
1971 # check is done by verifying the VNC ticket (inside VNC protocol).
1973 my $port = $param->{port
};
1975 return { port
=> $port };
1978 __PACKAGE__-
>register_method({
1979 name
=> 'spiceproxy',
1980 path
=> '{vmid}/spiceproxy',
1985 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1987 description
=> "Returns a SPICE configuration to connect to the VM.",
1989 additionalProperties
=> 0,
1991 node
=> get_standard_option
('pve-node'),
1992 vmid
=> get_standard_option
('pve-vmid'),
1993 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1996 returns
=> get_standard_option
('remote-viewer-config'),
2000 my $rpcenv = PVE
::RPCEnvironment
::get
();
2002 my $authuser = $rpcenv->get_user();
2004 my $vmid = $param->{vmid
};
2005 my $node = $param->{node
};
2006 my $proxy = $param->{proxy
};
2008 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
2009 my $title = "VM $vmid";
2010 $title .= " - ". $conf->{name
} if $conf->{name
};
2012 my $port = PVE
::QemuServer
::spice_port
($vmid);
2014 my ($ticket, undef, $remote_viewer_config) =
2015 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2017 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2018 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2020 return $remote_viewer_config;
2023 __PACKAGE__-
>register_method({
2025 path
=> '{vmid}/status',
2028 description
=> "Directory index",
2033 additionalProperties
=> 0,
2035 node
=> get_standard_option
('pve-node'),
2036 vmid
=> get_standard_option
('pve-vmid'),
2044 subdir
=> { type
=> 'string' },
2047 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2053 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2056 { subdir
=> 'current' },
2057 { subdir
=> 'start' },
2058 { subdir
=> 'stop' },
2059 { subdir
=> 'reset' },
2060 { subdir
=> 'shutdown' },
2061 { subdir
=> 'suspend' },
2062 { subdir
=> 'reboot' },
2068 __PACKAGE__-
>register_method({
2069 name
=> 'vm_status',
2070 path
=> '{vmid}/status/current',
2073 protected
=> 1, # qemu pid files are only readable by root
2074 description
=> "Get virtual machine status.",
2076 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2079 additionalProperties
=> 0,
2081 node
=> get_standard_option
('pve-node'),
2082 vmid
=> get_standard_option
('pve-vmid'),
2088 %$PVE::QemuServer
::vmstatus_return_properties
,
2090 description
=> "HA manager service status.",
2094 description
=> "Qemu VGA configuration supports spice.",
2099 description
=> "Qemu GuestAgent enabled in config.",
2109 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2111 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2112 my $status = $vmstatus->{$param->{vmid
}};
2114 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2116 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2117 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2122 __PACKAGE__-
>register_method({
2124 path
=> '{vmid}/status/start',
2128 description
=> "Start virtual machine.",
2130 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2133 additionalProperties
=> 0,
2135 node
=> get_standard_option
('pve-node'),
2136 vmid
=> get_standard_option
('pve-vmid',
2137 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2138 skiplock
=> get_standard_option
('skiplock'),
2139 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2140 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2143 enum
=> ['secure', 'insecure'],
2144 description
=> "Migration traffic is encrypted using an SSH " .
2145 "tunnel by default. On secure, completely private networks " .
2146 "this can be disabled to increase performance.",
2149 migration_network
=> {
2150 type
=> 'string', format
=> 'CIDR',
2151 description
=> "CIDR of the (sub) network that is used for migration.",
2154 machine
=> get_standard_option
('pve-qemu-machine'),
2156 description
=> "Override QEMU's -cpu argument with the given string.",
2160 targetstorage
=> get_standard_option
('pve-targetstorage'),
2162 description
=> "Wait maximal timeout seconds.",
2165 default => 'max(30, vm memory in GiB)',
2176 my $rpcenv = PVE
::RPCEnvironment
::get
();
2177 my $authuser = $rpcenv->get_user();
2179 my $node = extract_param
($param, 'node');
2180 my $vmid = extract_param
($param, 'vmid');
2181 my $timeout = extract_param
($param, 'timeout');
2183 my $machine = extract_param
($param, 'machine');
2184 my $force_cpu = extract_param
($param, 'force-cpu');
2186 my $get_root_param = sub {
2187 my $value = extract_param
($param, $_[0]);
2188 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2189 if $value && $authuser ne 'root@pam';
2193 my $stateuri = $get_root_param->('stateuri');
2194 my $skiplock = $get_root_param->('skiplock');
2195 my $migratedfrom = $get_root_param->('migratedfrom');
2196 my $migration_type = $get_root_param->('migration_type');
2197 my $migration_network = $get_root_param->('migration_network');
2198 my $targetstorage = $get_root_param->('targetstorage');
2202 if ($targetstorage) {
2203 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2205 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2206 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2210 # read spice ticket from STDIN
2212 my $nbd_protocol_version = 0;
2213 my $replicated_volumes = {};
2214 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2215 while (defined(my $line = <STDIN
>)) {
2217 if ($line =~ m/^spice_ticket: (.+)$/) {
2219 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2220 $nbd_protocol_version = $1;
2221 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2222 $replicated_volumes->{$1} = 1;
2224 # fallback for old source node
2225 $spice_ticket = $line;
2230 PVE
::Cluster
::check_cfs_quorum
();
2232 my $storecfg = PVE
::Storage
::config
();
2234 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2238 print "Requesting HA start for VM $vmid\n";
2240 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2241 PVE
::Tools
::run_command
($cmd);
2245 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2252 syslog
('info', "start VM $vmid: $upid\n");
2254 my $migrate_opts = {
2255 migratedfrom
=> $migratedfrom,
2256 spice_ticket
=> $spice_ticket,
2257 network
=> $migration_network,
2258 type
=> $migration_type,
2259 storagemap
=> $storagemap,
2260 nbd_proto_version
=> $nbd_protocol_version,
2261 replicated_volumes
=> $replicated_volumes,
2265 statefile
=> $stateuri,
2266 skiplock
=> $skiplock,
2267 forcemachine
=> $machine,
2268 timeout
=> $timeout,
2269 forcecpu
=> $force_cpu,
2272 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2276 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2280 __PACKAGE__-
>register_method({
2282 path
=> '{vmid}/status/stop',
2286 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2287 "is akin to pulling the power plug of a running computer and may damage the VM data",
2289 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2292 additionalProperties
=> 0,
2294 node
=> get_standard_option
('pve-node'),
2295 vmid
=> get_standard_option
('pve-vmid',
2296 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2297 skiplock
=> get_standard_option
('skiplock'),
2298 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2300 description
=> "Wait maximal timeout seconds.",
2306 description
=> "Do not deactivate storage volumes.",
2319 my $rpcenv = PVE
::RPCEnvironment
::get
();
2320 my $authuser = $rpcenv->get_user();
2322 my $node = extract_param
($param, 'node');
2323 my $vmid = extract_param
($param, 'vmid');
2325 my $skiplock = extract_param
($param, 'skiplock');
2326 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2327 if $skiplock && $authuser ne 'root@pam';
2329 my $keepActive = extract_param
($param, 'keepActive');
2330 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2331 if $keepActive && $authuser ne 'root@pam';
2333 my $migratedfrom = extract_param
($param, 'migratedfrom');
2334 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2335 if $migratedfrom && $authuser ne 'root@pam';
2338 my $storecfg = PVE
::Storage
::config
();
2340 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2345 print "Requesting HA stop for VM $vmid\n";
2347 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2348 PVE
::Tools
::run_command
($cmd);
2352 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2358 syslog
('info', "stop VM $vmid: $upid\n");
2360 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2361 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2365 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2369 __PACKAGE__-
>register_method({
2371 path
=> '{vmid}/status/reset',
2375 description
=> "Reset virtual machine.",
2377 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2380 additionalProperties
=> 0,
2382 node
=> get_standard_option
('pve-node'),
2383 vmid
=> get_standard_option
('pve-vmid',
2384 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2385 skiplock
=> get_standard_option
('skiplock'),
2394 my $rpcenv = PVE
::RPCEnvironment
::get
();
2396 my $authuser = $rpcenv->get_user();
2398 my $node = extract_param
($param, 'node');
2400 my $vmid = extract_param
($param, 'vmid');
2402 my $skiplock = extract_param
($param, 'skiplock');
2403 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2404 if $skiplock && $authuser ne 'root@pam';
2406 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2411 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2416 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2419 my sub vm_is_paused
{
2421 my $qmpstatus = eval {
2422 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2423 mon_cmd
($vmid, "query-status");
2426 return $qmpstatus && $qmpstatus->{status
} eq "paused";
2429 __PACKAGE__-
>register_method({
2430 name
=> 'vm_shutdown',
2431 path
=> '{vmid}/status/shutdown',
2435 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2436 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2438 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2441 additionalProperties
=> 0,
2443 node
=> get_standard_option
('pve-node'),
2444 vmid
=> get_standard_option
('pve-vmid',
2445 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2446 skiplock
=> get_standard_option
('skiplock'),
2448 description
=> "Wait maximal timeout seconds.",
2454 description
=> "Make sure the VM stops.",
2460 description
=> "Do not deactivate storage volumes.",
2473 my $rpcenv = PVE
::RPCEnvironment
::get
();
2474 my $authuser = $rpcenv->get_user();
2476 my $node = extract_param
($param, 'node');
2477 my $vmid = extract_param
($param, 'vmid');
2479 my $skiplock = extract_param
($param, 'skiplock');
2480 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2481 if $skiplock && $authuser ne 'root@pam';
2483 my $keepActive = extract_param
($param, 'keepActive');
2484 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2485 if $keepActive && $authuser ne 'root@pam';
2487 my $storecfg = PVE
::Storage
::config
();
2491 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2492 # otherwise, we will infer a shutdown command, but run into the timeout,
2493 # then when the vm is resumed, it will instantly shutdown
2495 # checking the qmp status here to get feedback to the gui/cli/api
2496 # and the status query should not take too long
2497 if (vm_is_paused
($vmid)) {
2498 if ($param->{forceStop
}) {
2499 warn "VM is paused - stop instead of shutdown\n";
2502 die "VM is paused - cannot shutdown\n";
2506 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2508 my $timeout = $param->{timeout
} // 60;
2512 print "Requesting HA stop for VM $vmid\n";
2514 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2515 PVE
::Tools
::run_command
($cmd);
2519 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2526 syslog
('info', "shutdown VM $vmid: $upid\n");
2528 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2529 $shutdown, $param->{forceStop
}, $keepActive);
2533 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2537 __PACKAGE__-
>register_method({
2538 name
=> 'vm_reboot',
2539 path
=> '{vmid}/status/reboot',
2543 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2545 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2548 additionalProperties
=> 0,
2550 node
=> get_standard_option
('pve-node'),
2551 vmid
=> get_standard_option
('pve-vmid',
2552 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2554 description
=> "Wait maximal timeout seconds for the shutdown.",
2567 my $rpcenv = PVE
::RPCEnvironment
::get
();
2568 my $authuser = $rpcenv->get_user();
2570 my $node = extract_param
($param, 'node');
2571 my $vmid = extract_param
($param, 'vmid');
2573 die "VM is paused - cannot shutdown\n" if vm_is_paused
($vmid);
2575 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2580 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2581 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2585 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2588 __PACKAGE__-
>register_method({
2589 name
=> 'vm_suspend',
2590 path
=> '{vmid}/status/suspend',
2594 description
=> "Suspend virtual machine.",
2596 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2597 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2598 " on the storage for the vmstate.",
2599 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2602 additionalProperties
=> 0,
2604 node
=> get_standard_option
('pve-node'),
2605 vmid
=> get_standard_option
('pve-vmid',
2606 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2607 skiplock
=> get_standard_option
('skiplock'),
2612 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2614 statestorage
=> get_standard_option
('pve-storage-id', {
2615 description
=> "The storage for the VM state",
2616 requires
=> 'todisk',
2618 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2628 my $rpcenv = PVE
::RPCEnvironment
::get
();
2629 my $authuser = $rpcenv->get_user();
2631 my $node = extract_param
($param, 'node');
2632 my $vmid = extract_param
($param, 'vmid');
2634 my $todisk = extract_param
($param, 'todisk') // 0;
2636 my $statestorage = extract_param
($param, 'statestorage');
2638 my $skiplock = extract_param
($param, 'skiplock');
2639 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2640 if $skiplock && $authuser ne 'root@pam';
2642 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2644 die "Cannot suspend HA managed VM to disk\n"
2645 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2647 # early check for storage permission, for better user feedback
2649 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2651 if (!$statestorage) {
2652 # get statestorage from config if none is given
2653 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2654 my $storecfg = PVE
::Storage
::config
();
2655 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2658 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2664 syslog
('info', "suspend VM $vmid: $upid\n");
2666 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2671 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2672 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2675 __PACKAGE__-
>register_method({
2676 name
=> 'vm_resume',
2677 path
=> '{vmid}/status/resume',
2681 description
=> "Resume virtual machine.",
2683 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2686 additionalProperties
=> 0,
2688 node
=> get_standard_option
('pve-node'),
2689 vmid
=> get_standard_option
('pve-vmid',
2690 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2691 skiplock
=> get_standard_option
('skiplock'),
2692 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2702 my $rpcenv = PVE
::RPCEnvironment
::get
();
2704 my $authuser = $rpcenv->get_user();
2706 my $node = extract_param
($param, 'node');
2708 my $vmid = extract_param
($param, 'vmid');
2710 my $skiplock = extract_param
($param, 'skiplock');
2711 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2712 if $skiplock && $authuser ne 'root@pam';
2714 my $nocheck = extract_param
($param, 'nocheck');
2715 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2716 if $nocheck && $authuser ne 'root@pam';
2718 my $to_disk_suspended;
2720 PVE
::QemuConfig-
>lock_config($vmid, sub {
2721 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2722 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2726 die "VM $vmid not running\n"
2727 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2732 syslog
('info', "resume VM $vmid: $upid\n");
2734 if (!$to_disk_suspended) {
2735 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2737 my $storecfg = PVE
::Storage
::config
();
2738 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2744 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2747 __PACKAGE__-
>register_method({
2748 name
=> 'vm_sendkey',
2749 path
=> '{vmid}/sendkey',
2753 description
=> "Send key event to virtual machine.",
2755 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2758 additionalProperties
=> 0,
2760 node
=> get_standard_option
('pve-node'),
2761 vmid
=> get_standard_option
('pve-vmid',
2762 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2763 skiplock
=> get_standard_option
('skiplock'),
2765 description
=> "The key (qemu monitor encoding).",
2770 returns
=> { type
=> 'null'},
2774 my $rpcenv = PVE
::RPCEnvironment
::get
();
2776 my $authuser = $rpcenv->get_user();
2778 my $node = extract_param
($param, 'node');
2780 my $vmid = extract_param
($param, 'vmid');
2782 my $skiplock = extract_param
($param, 'skiplock');
2783 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2784 if $skiplock && $authuser ne 'root@pam';
2786 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2791 __PACKAGE__-
>register_method({
2792 name
=> 'vm_feature',
2793 path
=> '{vmid}/feature',
2797 description
=> "Check if feature for virtual machine is available.",
2799 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2802 additionalProperties
=> 0,
2804 node
=> get_standard_option
('pve-node'),
2805 vmid
=> get_standard_option
('pve-vmid'),
2807 description
=> "Feature to check.",
2809 enum
=> [ 'snapshot', 'clone', 'copy' ],
2811 snapname
=> get_standard_option
('pve-snapshot-name', {
2819 hasFeature
=> { type
=> 'boolean' },
2822 items
=> { type
=> 'string' },
2829 my $node = extract_param
($param, 'node');
2831 my $vmid = extract_param
($param, 'vmid');
2833 my $snapname = extract_param
($param, 'snapname');
2835 my $feature = extract_param
($param, 'feature');
2837 my $running = PVE
::QemuServer
::check_running
($vmid);
2839 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2842 my $snap = $conf->{snapshots
}->{$snapname};
2843 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2846 my $storecfg = PVE
::Storage
::config
();
2848 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2849 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2852 hasFeature
=> $hasFeature,
2853 nodes
=> [ keys %$nodelist ],
2857 __PACKAGE__-
>register_method({
2859 path
=> '{vmid}/clone',
2863 description
=> "Create a copy of virtual machine/template.",
2865 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2866 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2867 "'Datastore.AllocateSpace' on any used storage.",
2870 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2872 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2873 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2878 additionalProperties
=> 0,
2880 node
=> get_standard_option
('pve-node'),
2881 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2882 newid
=> get_standard_option
('pve-vmid', {
2883 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2884 description
=> 'VMID for the clone.' }),
2887 type
=> 'string', format
=> 'dns-name',
2888 description
=> "Set a name for the new VM.",
2893 description
=> "Description for the new VM.",
2897 type
=> 'string', format
=> 'pve-poolid',
2898 description
=> "Add the new VM to the specified pool.",
2900 snapname
=> get_standard_option
('pve-snapshot-name', {
2903 storage
=> get_standard_option
('pve-storage-id', {
2904 description
=> "Target storage for full clone.",
2908 description
=> "Target format for file storage. Only valid for full clone.",
2911 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2916 description
=> "Create a full copy of all disks. This is always done when " .
2917 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2919 target
=> get_standard_option
('pve-node', {
2920 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2924 description
=> "Override I/O bandwidth limit (in KiB/s).",
2928 default => 'clone limit from datacenter or storage config',
2938 my $rpcenv = PVE
::RPCEnvironment
::get
();
2939 my $authuser = $rpcenv->get_user();
2941 my $node = extract_param
($param, 'node');
2942 my $vmid = extract_param
($param, 'vmid');
2943 my $newid = extract_param
($param, 'newid');
2944 my $pool = extract_param
($param, 'pool');
2945 $rpcenv->check_pool_exist($pool) if defined($pool);
2947 my $snapname = extract_param
($param, 'snapname');
2948 my $storage = extract_param
($param, 'storage');
2949 my $format = extract_param
($param, 'format');
2950 my $target = extract_param
($param, 'target');
2952 my $localnode = PVE
::INotify
::nodename
();
2954 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2958 PVE
::Cluster
::check_node_exists
($target) if $target;
2960 my $storecfg = PVE
::Storage
::config
();
2963 # check if storage is enabled on local node
2964 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2966 # check if storage is available on target node
2967 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2968 # clone only works if target storage is shared
2969 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2970 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2974 PVE
::Cluster
::check_cfs_quorum
();
2976 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2979 # do all tests after lock but before forking worker - if possible
2981 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2982 PVE
::QemuConfig-
>check_lock($conf);
2984 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2985 die "unexpected state change\n" if $verify_running != $running;
2987 die "snapshot '$snapname' does not exist\n"
2988 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2990 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2992 die "parameter 'storage' not allowed for linked clones\n"
2993 if defined($storage) && !$full;
2995 die "parameter 'format' not allowed for linked clones\n"
2996 if defined($format) && !$full;
2998 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
3000 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
3002 die "can't clone VM to node '$target' (VM uses local storage)\n"
3003 if $target && !$sharedvm;
3005 my $conffile = PVE
::QemuConfig-
>config_file($newid);
3006 die "unable to create VM $newid: config file already exists\n"
3009 my $newconf = { lock => 'clone' };
3014 foreach my $opt (keys %$oldconf) {
3015 my $value = $oldconf->{$opt};
3017 # do not copy snapshot related info
3018 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3019 $opt eq 'vmstate' || $opt eq 'snapstate';
3021 # no need to copy unused images, because VMID(owner) changes anyways
3022 next if $opt =~ m/^unused\d+$/;
3024 # always change MAC! address
3025 if ($opt =~ m/^net(\d+)$/) {
3026 my $net = PVE
::QemuServer
::parse_net
($value);
3027 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3028 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3029 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3030 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3031 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3032 die "unable to parse drive options for '$opt'\n" if !$drive;
3033 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3034 $newconf->{$opt} = $value; # simply copy configuration
3036 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3037 die "Full clone feature is not supported for drive '$opt'\n"
3038 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3039 $fullclone->{$opt} = 1;
3041 # not full means clone instead of copy
3042 die "Linked clone feature is not supported for drive '$opt'\n"
3043 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3045 $drives->{$opt} = $drive;
3046 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3047 push @$vollist, $drive->{file
};
3050 # copy everything else
3051 $newconf->{$opt} = $value;
3055 # auto generate a new uuid
3056 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3057 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3058 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3059 # auto generate a new vmgenid only if the option was set for template
3060 if ($newconf->{vmgenid
}) {
3061 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3064 delete $newconf->{template
};
3066 if ($param->{name
}) {
3067 $newconf->{name
} = $param->{name
};
3069 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3072 if ($param->{description
}) {
3073 $newconf->{description
} = $param->{description
};
3076 # create empty/temp config - this fails if VM already exists on other node
3077 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3078 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3083 my $newvollist = [];
3090 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3092 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3094 my $bwlimit = extract_param
($param, 'bwlimit');
3096 my $total_jobs = scalar(keys %{$drives});
3099 foreach my $opt (keys %$drives) {
3100 my $drive = $drives->{$opt};
3101 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3102 my $completion = $skipcomplete ?
'skip' : 'complete';
3104 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3105 my $storage_list = [ $src_sid ];
3106 push @$storage_list, $storage if defined($storage);
3107 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3109 my $newdrive = PVE
::QemuServer
::clone_disk
(
3128 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3130 PVE
::QemuConfig-
>write_config($newid, $newconf);
3134 delete $newconf->{lock};
3136 # do not write pending changes
3137 if (my @changes = keys %{$newconf->{pending
}}) {
3138 my $pending = join(',', @changes);
3139 warn "found pending changes for '$pending', discarding for clone\n";
3140 delete $newconf->{pending
};
3143 PVE
::QemuConfig-
>write_config($newid, $newconf);
3146 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3147 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3148 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3150 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3151 die "Failed to move config to node '$target' - rename failed: $!\n"
3152 if !rename($conffile, $newconffile);
3155 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3158 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3159 sleep 1; # some storage like rbd need to wait before release volume - really?
3161 foreach my $volid (@$newvollist) {
3162 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3166 PVE
::Firewall
::remove_vmfw_conf
($newid);
3168 unlink $conffile; # avoid races -> last thing before die
3170 die "clone failed: $err";
3176 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3178 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3181 # Aquire exclusive lock lock for $newid
3182 my $lock_target_vm = sub {
3183 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3186 # exclusive lock if VM is running - else shared lock is enough;
3188 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3190 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3194 __PACKAGE__-
>register_method({
3195 name
=> 'move_vm_disk',
3196 path
=> '{vmid}/move_disk',
3200 description
=> "Move volume to different storage.",
3202 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3204 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3205 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3209 additionalProperties
=> 0,
3211 node
=> get_standard_option
('pve-node'),
3212 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3215 description
=> "The disk you want to move.",
3216 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3218 storage
=> get_standard_option
('pve-storage-id', {
3219 description
=> "Target storage.",
3220 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3224 description
=> "Target Format.",
3225 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3230 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3236 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3241 description
=> "Override I/O bandwidth limit (in KiB/s).",
3245 default => 'move limit from datacenter or storage config',
3251 description
=> "the task ID.",
3256 my $rpcenv = PVE
::RPCEnvironment
::get
();
3257 my $authuser = $rpcenv->get_user();
3259 my $node = extract_param
($param, 'node');
3260 my $vmid = extract_param
($param, 'vmid');
3261 my $digest = extract_param
($param, 'digest');
3262 my $disk = extract_param
($param, 'disk');
3263 my $storeid = extract_param
($param, 'storage');
3264 my $format = extract_param
($param, 'format');
3266 my $storecfg = PVE
::Storage
::config
();
3268 my $updatefn = sub {
3269 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3270 PVE
::QemuConfig-
>check_lock($conf);
3272 die "VM config checksum missmatch (file change by other user?)\n"
3273 if $digest && $digest ne $conf->{digest
};
3275 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3277 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3279 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3280 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3282 my $old_volid = $drive->{file
};
3284 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3285 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3289 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3290 (!$format || !$oldfmt || $oldfmt eq $format);
3292 # this only checks snapshots because $disk is passed!
3293 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3294 die "you can't move a disk with snapshots and delete the source\n"
3295 if $snapshotted && $param->{delete};
3297 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3299 my $running = PVE
::QemuServer
::check_running
($vmid);
3301 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3304 my $newvollist = [];
3310 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3312 warn "moving disk with snapshots, snapshots will not be moved!\n"
3315 my $bwlimit = extract_param
($param, 'bwlimit');
3316 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3318 my $newdrive = PVE
::QemuServer
::clone_disk
(
3336 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3338 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3340 # convert moved disk to base if part of template
3341 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3342 if PVE
::QemuConfig-
>is_template($conf);
3344 PVE
::QemuConfig-
>write_config($vmid, $conf);
3346 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3347 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3348 eval { mon_cmd
($vmid, "guest-fstrim") };
3352 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3353 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3359 foreach my $volid (@$newvollist) {
3360 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3363 die "storage migration failed: $err";
3366 if ($param->{delete}) {
3368 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3369 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3375 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3378 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3381 my $check_vm_disks_local = sub {
3382 my ($storecfg, $vmconf, $vmid) = @_;
3384 my $local_disks = {};
3386 # add some more information to the disks e.g. cdrom
3387 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3388 my ($volid, $attr) = @_;
3390 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3392 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3393 return if $scfg->{shared
};
3395 # The shared attr here is just a special case where the vdisk
3396 # is marked as shared manually
3397 return if $attr->{shared
};
3398 return if $attr->{cdrom
} and $volid eq "none";
3400 if (exists $local_disks->{$volid}) {
3401 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3403 $local_disks->{$volid} = $attr;
3404 # ensure volid is present in case it's needed
3405 $local_disks->{$volid}->{volid
} = $volid;
3409 return $local_disks;
3412 __PACKAGE__-
>register_method({
3413 name
=> 'migrate_vm_precondition',
3414 path
=> '{vmid}/migrate',
3418 description
=> "Get preconditions for migration.",
3420 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3423 additionalProperties
=> 0,
3425 node
=> get_standard_option
('pve-node'),
3426 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3427 target
=> get_standard_option
('pve-node', {
3428 description
=> "Target node.",
3429 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3437 running
=> { type
=> 'boolean' },
3441 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3443 not_allowed_nodes
=> {
3446 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3450 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3452 local_resources
=> {
3454 description
=> "List local resources e.g. pci, usb"
3461 my $rpcenv = PVE
::RPCEnvironment
::get
();
3463 my $authuser = $rpcenv->get_user();
3465 PVE
::Cluster
::check_cfs_quorum
();
3469 my $vmid = extract_param
($param, 'vmid');
3470 my $target = extract_param
($param, 'target');
3471 my $localnode = PVE
::INotify
::nodename
();
3475 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3476 my $storecfg = PVE
::Storage
::config
();
3479 # try to detect errors early
3480 PVE
::QemuConfig-
>check_lock($vmconf);
3482 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3484 # if vm is not running, return target nodes where local storage is available
3485 # for offline migration
3486 if (!$res->{running
}) {
3487 $res->{allowed_nodes
} = [];
3488 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3489 delete $checked_nodes->{$localnode};
3491 foreach my $node (keys %$checked_nodes) {
3492 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3493 push @{$res->{allowed_nodes
}}, $node;
3497 $res->{not_allowed_nodes
} = $checked_nodes;
3501 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3502 $res->{local_disks
} = [ values %$local_disks ];;
3504 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3506 $res->{local_resources
} = $local_resources;
3513 __PACKAGE__-
>register_method({
3514 name
=> 'migrate_vm',
3515 path
=> '{vmid}/migrate',
3519 description
=> "Migrate virtual machine. Creates a new migration task.",
3521 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3524 additionalProperties
=> 0,
3526 node
=> get_standard_option
('pve-node'),
3527 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3528 target
=> get_standard_option
('pve-node', {
3529 description
=> "Target node.",
3530 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3534 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3539 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3544 enum
=> ['secure', 'insecure'],
3545 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3548 migration_network
=> {
3549 type
=> 'string', format
=> 'CIDR',
3550 description
=> "CIDR of the (sub) network that is used for migration.",
3553 "with-local-disks" => {
3555 description
=> "Enable live storage migration for local disk",
3558 targetstorage
=> get_standard_option
('pve-targetstorage', {
3559 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3562 description
=> "Override I/O bandwidth limit (in KiB/s).",
3566 default => 'migrate limit from datacenter or storage config',
3572 description
=> "the task ID.",
3577 my $rpcenv = PVE
::RPCEnvironment
::get
();
3578 my $authuser = $rpcenv->get_user();
3580 my $target = extract_param
($param, 'target');
3582 my $localnode = PVE
::INotify
::nodename
();
3583 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3585 PVE
::Cluster
::check_cfs_quorum
();
3587 PVE
::Cluster
::check_node_exists
($target);
3589 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3591 my $vmid = extract_param
($param, 'vmid');
3593 raise_param_exc
({ force
=> "Only root may use this option." })
3594 if $param->{force
} && $authuser ne 'root@pam';
3596 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3597 if $param->{migration_type
} && $authuser ne 'root@pam';
3599 # allow root only until better network permissions are available
3600 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3601 if $param->{migration_network
} && $authuser ne 'root@pam';
3604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3606 # try to detect errors early
3608 PVE
::QemuConfig-
>check_lock($conf);
3610 if (PVE
::QemuServer
::check_running
($vmid)) {
3611 die "can't migrate running VM without --online\n" if !$param->{online
};
3613 my $repl_conf = PVE
::ReplicationConfig-
>new();
3614 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3615 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3616 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3617 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3618 "target. Use 'force' to override.\n";
3621 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3622 $param->{online
} = 0;
3625 my $storecfg = PVE
::Storage
::config
();
3627 if (my $targetstorage = $param->{targetstorage
}) {
3628 my $check_storage = sub {
3629 my ($target_sid) = @_;
3630 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3631 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3632 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3633 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3634 if !$scfg->{content
}->{images
};
3637 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3638 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3641 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3642 if !defined($storagemap->{identity
});
3644 foreach my $source (values %{$storagemap->{entries
}}) {
3645 $check_storage->($source);
3648 $check_storage->($storagemap->{default})
3649 if $storagemap->{default};
3651 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3652 if $storagemap->{identity
};
3654 $param->{storagemap
} = $storagemap;
3656 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3659 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3664 print "Requesting HA migration for VM $vmid to node $target\n";
3666 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3667 PVE
::Tools
::run_command
($cmd);
3671 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3676 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3680 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3683 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3688 __PACKAGE__-
>register_method({
3690 path
=> '{vmid}/monitor',
3694 description
=> "Execute Qemu monitor commands.",
3696 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3700 additionalProperties
=> 0,
3702 node
=> get_standard_option
('pve-node'),
3703 vmid
=> get_standard_option
('pve-vmid'),
3706 description
=> "The monitor command.",
3710 returns
=> { type
=> 'string'},
3714 my $rpcenv = PVE
::RPCEnvironment
::get
();
3715 my $authuser = $rpcenv->get_user();
3718 my $command = shift;
3719 return $command =~ m/^\s*info(\s+|$)/
3720 || $command =~ m/^\s*help\s*$/;
3723 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3724 if !&$is_ro($param->{command
});
3726 my $vmid = $param->{vmid
};
3728 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3732 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3734 $res = "ERROR: $@" if $@;
3739 __PACKAGE__-
>register_method({
3740 name
=> 'resize_vm',
3741 path
=> '{vmid}/resize',
3745 description
=> "Extend volume size.",
3747 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3750 additionalProperties
=> 0,
3752 node
=> get_standard_option
('pve-node'),
3753 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3754 skiplock
=> get_standard_option
('skiplock'),
3757 description
=> "The disk you want to resize.",
3758 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3762 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3763 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.",
3767 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3773 returns
=> { type
=> 'null'},
3777 my $rpcenv = PVE
::RPCEnvironment
::get
();
3779 my $authuser = $rpcenv->get_user();
3781 my $node = extract_param
($param, 'node');
3783 my $vmid = extract_param
($param, 'vmid');
3785 my $digest = extract_param
($param, 'digest');
3787 my $disk = extract_param
($param, 'disk');
3789 my $sizestr = extract_param
($param, 'size');
3791 my $skiplock = extract_param
($param, 'skiplock');
3792 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3793 if $skiplock && $authuser ne 'root@pam';
3795 my $storecfg = PVE
::Storage
::config
();
3797 my $updatefn = sub {
3799 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3801 die "checksum missmatch (file change by other user?)\n"
3802 if $digest && $digest ne $conf->{digest
};
3803 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3805 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3807 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3809 my (undef, undef, undef, undef, undef, undef, $format) =
3810 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3812 die "can't resize volume: $disk if snapshot exists\n"
3813 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3815 my $volid = $drive->{file
};
3817 die "disk '$disk' has no associated volume\n" if !$volid;
3819 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3821 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3823 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3825 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3826 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3828 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3830 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3831 my ($ext, $newsize, $unit) = ($1, $2, $4);
3834 $newsize = $newsize * 1024;
3835 } elsif ($unit eq 'M') {
3836 $newsize = $newsize * 1024 * 1024;
3837 } elsif ($unit eq 'G') {
3838 $newsize = $newsize * 1024 * 1024 * 1024;
3839 } elsif ($unit eq 'T') {
3840 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3843 $newsize += $size if $ext;
3844 $newsize = int($newsize);
3846 die "shrinking disks is not supported\n" if $newsize < $size;
3848 return if $size == $newsize;
3850 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3852 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3854 $drive->{size
} = $newsize;
3855 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3857 PVE
::QemuConfig-
>write_config($vmid, $conf);
3860 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3864 __PACKAGE__-
>register_method({
3865 name
=> 'snapshot_list',
3866 path
=> '{vmid}/snapshot',
3868 description
=> "List all snapshots.",
3870 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3873 protected
=> 1, # qemu pid files are only readable by root
3875 additionalProperties
=> 0,
3877 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3878 node
=> get_standard_option
('pve-node'),
3887 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3891 description
=> "Snapshot includes RAM.",
3896 description
=> "Snapshot description.",
3900 description
=> "Snapshot creation time",
3902 renderer
=> 'timestamp',
3906 description
=> "Parent snapshot identifier.",
3912 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3917 my $vmid = $param->{vmid
};
3919 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3920 my $snaphash = $conf->{snapshots
} || {};
3924 foreach my $name (keys %$snaphash) {
3925 my $d = $snaphash->{$name};
3928 snaptime
=> $d->{snaptime
} || 0,
3929 vmstate
=> $d->{vmstate
} ?
1 : 0,
3930 description
=> $d->{description
} || '',
3932 $item->{parent
} = $d->{parent
} if $d->{parent
};
3933 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3937 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3940 digest
=> $conf->{digest
},
3941 running
=> $running,
3942 description
=> "You are here!",
3944 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3946 push @$res, $current;
3951 __PACKAGE__-
>register_method({
3953 path
=> '{vmid}/snapshot',
3957 description
=> "Snapshot a VM.",
3959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3962 additionalProperties
=> 0,
3964 node
=> get_standard_option
('pve-node'),
3965 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3966 snapname
=> get_standard_option
('pve-snapshot-name'),
3970 description
=> "Save the vmstate",
3975 description
=> "A textual description or comment.",
3981 description
=> "the task ID.",
3986 my $rpcenv = PVE
::RPCEnvironment
::get
();
3988 my $authuser = $rpcenv->get_user();
3990 my $node = extract_param
($param, 'node');
3992 my $vmid = extract_param
($param, 'vmid');
3994 my $snapname = extract_param
($param, 'snapname');
3996 die "unable to use snapshot name 'current' (reserved name)\n"
3997 if $snapname eq 'current';
3999 die "unable to use snapshot name 'pending' (reserved name)\n"
4000 if lc($snapname) eq 'pending';
4003 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
4004 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
4005 $param->{description
});
4008 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
4011 __PACKAGE__-
>register_method({
4012 name
=> 'snapshot_cmd_idx',
4013 path
=> '{vmid}/snapshot/{snapname}',
4020 additionalProperties
=> 0,
4022 vmid
=> get_standard_option
('pve-vmid'),
4023 node
=> get_standard_option
('pve-node'),
4024 snapname
=> get_standard_option
('pve-snapshot-name'),
4033 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4040 push @$res, { cmd
=> 'rollback' };
4041 push @$res, { cmd
=> 'config' };
4046 __PACKAGE__-
>register_method({
4047 name
=> 'update_snapshot_config',
4048 path
=> '{vmid}/snapshot/{snapname}/config',
4052 description
=> "Update snapshot metadata.",
4054 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4057 additionalProperties
=> 0,
4059 node
=> get_standard_option
('pve-node'),
4060 vmid
=> get_standard_option
('pve-vmid'),
4061 snapname
=> get_standard_option
('pve-snapshot-name'),
4065 description
=> "A textual description or comment.",
4069 returns
=> { type
=> 'null' },
4073 my $rpcenv = PVE
::RPCEnvironment
::get
();
4075 my $authuser = $rpcenv->get_user();
4077 my $vmid = extract_param
($param, 'vmid');
4079 my $snapname = extract_param
($param, 'snapname');
4081 return if !defined($param->{description
});
4083 my $updatefn = sub {
4085 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4087 PVE
::QemuConfig-
>check_lock($conf);
4089 my $snap = $conf->{snapshots
}->{$snapname};
4091 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4093 $snap->{description
} = $param->{description
} if defined($param->{description
});
4095 PVE
::QemuConfig-
>write_config($vmid, $conf);
4098 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4103 __PACKAGE__-
>register_method({
4104 name
=> 'get_snapshot_config',
4105 path
=> '{vmid}/snapshot/{snapname}/config',
4108 description
=> "Get snapshot configuration",
4110 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4113 additionalProperties
=> 0,
4115 node
=> get_standard_option
('pve-node'),
4116 vmid
=> get_standard_option
('pve-vmid'),
4117 snapname
=> get_standard_option
('pve-snapshot-name'),
4120 returns
=> { type
=> "object" },
4124 my $rpcenv = PVE
::RPCEnvironment
::get
();
4126 my $authuser = $rpcenv->get_user();
4128 my $vmid = extract_param
($param, 'vmid');
4130 my $snapname = extract_param
($param, 'snapname');
4132 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4134 my $snap = $conf->{snapshots
}->{$snapname};
4136 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4141 __PACKAGE__-
>register_method({
4143 path
=> '{vmid}/snapshot/{snapname}/rollback',
4147 description
=> "Rollback VM state to specified snapshot.",
4149 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4152 additionalProperties
=> 0,
4154 node
=> get_standard_option
('pve-node'),
4155 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4156 snapname
=> get_standard_option
('pve-snapshot-name'),
4161 description
=> "the task ID.",
4166 my $rpcenv = PVE
::RPCEnvironment
::get
();
4168 my $authuser = $rpcenv->get_user();
4170 my $node = extract_param
($param, 'node');
4172 my $vmid = extract_param
($param, 'vmid');
4174 my $snapname = extract_param
($param, 'snapname');
4177 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4178 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4182 # hold migration lock, this makes sure that nobody create replication snapshots
4183 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4186 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4189 __PACKAGE__-
>register_method({
4190 name
=> 'delsnapshot',
4191 path
=> '{vmid}/snapshot/{snapname}',
4195 description
=> "Delete a VM snapshot.",
4197 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4200 additionalProperties
=> 0,
4202 node
=> get_standard_option
('pve-node'),
4203 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4204 snapname
=> get_standard_option
('pve-snapshot-name'),
4208 description
=> "For removal from config file, even if removing disk snapshots fails.",
4214 description
=> "the task ID.",
4219 my $rpcenv = PVE
::RPCEnvironment
::get
();
4221 my $authuser = $rpcenv->get_user();
4223 my $node = extract_param
($param, 'node');
4225 my $vmid = extract_param
($param, 'vmid');
4227 my $snapname = extract_param
($param, 'snapname');
4230 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4231 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4234 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4237 __PACKAGE__-
>register_method({
4239 path
=> '{vmid}/template',
4243 description
=> "Create a Template.",
4245 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4246 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4249 additionalProperties
=> 0,
4251 node
=> get_standard_option
('pve-node'),
4252 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4256 description
=> "If you want to convert only 1 disk to base image.",
4257 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4262 returns
=> { type
=> 'null'},
4266 my $rpcenv = PVE
::RPCEnvironment
::get
();
4268 my $authuser = $rpcenv->get_user();
4270 my $node = extract_param
($param, 'node');
4272 my $vmid = extract_param
($param, 'vmid');
4274 my $disk = extract_param
($param, 'disk');
4276 my $updatefn = sub {
4278 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4280 PVE
::QemuConfig-
>check_lock($conf);
4282 die "unable to create template, because VM contains snapshots\n"
4283 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4285 die "you can't convert a template to a template\n"
4286 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4288 die "you can't convert a VM to template if VM is running\n"
4289 if PVE
::QemuServer
::check_running
($vmid);
4292 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4295 $conf->{template
} = 1;
4296 PVE
::QemuConfig-
>write_config($vmid, $conf);
4298 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4301 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4305 __PACKAGE__-
>register_method({
4306 name
=> 'cloudinit_generated_config_dump',
4307 path
=> '{vmid}/cloudinit/dump',
4310 description
=> "Get automatically generated cloudinit config.",
4312 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4315 additionalProperties
=> 0,
4317 node
=> get_standard_option
('pve-node'),
4318 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4320 description
=> 'Config type.',
4322 enum
=> ['user', 'network', 'meta'],
4332 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4334 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});