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 (also delete all used/owned volumes).",
1506 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1513 skiplock
=> get_standard_option
('skiplock'),
1516 description
=> "Remove VM ID from backup jobs, replication jobs and HA resource configuration.",
1527 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1529 my $vmid = $param->{vmid
};
1531 my $skiplock = $param->{skiplock
};
1532 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1533 if $skiplock && $authuser ne 'root@pam';
1535 my $early_checks = sub {
1537 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1538 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1540 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1542 if (!$param->{purge
}) {
1543 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1545 # don't allow destroy if with replication jobs but no purge param
1546 my $repl_conf = PVE
::ReplicationConfig-
>new();
1547 $repl_conf->check_for_existing_jobs($vmid);
1550 die "VM $vmid is running - destroy failed\n"
1551 if PVE
::QemuServer
::check_running
($vmid);
1561 my $storecfg = PVE
::Storage
::config
();
1563 syslog
('info', "destroy VM $vmid: $upid\n");
1564 PVE
::QemuConfig-
>lock_config($vmid, sub {
1565 # repeat, config might have changed
1566 my $ha_managed = $early_checks->();
1568 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1570 PVE
::AccessControl
::remove_vm_access
($vmid);
1571 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1572 if ($param->{purge
}) {
1573 print "purging VM $vmid from related configurations..\n";
1574 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1575 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1578 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1579 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1583 # only now remove the zombie config, else we can have reuse race
1584 PVE
::QemuConfig-
>destroy_config($vmid);
1588 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1591 __PACKAGE__-
>register_method({
1593 path
=> '{vmid}/unlink',
1597 description
=> "Unlink/delete disk images.",
1599 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1602 additionalProperties
=> 0,
1604 node
=> get_standard_option
('pve-node'),
1605 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1607 type
=> 'string', format
=> 'pve-configid-list',
1608 description
=> "A list of disk IDs you want to delete.",
1612 description
=> $opt_force_description,
1617 returns
=> { type
=> 'null'},
1621 $param->{delete} = extract_param
($param, 'idlist');
1623 __PACKAGE__-
>update_vm($param);
1628 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1629 my $gen_rand_chars = sub {
1632 die "invalid length $length" if $length < 1;
1634 my $min = ord('!'); # first printable ascii
1636 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1637 die "failed to generate random bytes!\n"
1640 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1647 __PACKAGE__-
>register_method({
1649 path
=> '{vmid}/vncproxy',
1653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1655 description
=> "Creates a TCP VNC proxy connections.",
1657 additionalProperties
=> 0,
1659 node
=> get_standard_option
('pve-node'),
1660 vmid
=> get_standard_option
('pve-vmid'),
1664 description
=> "starts websockify instead of vncproxy",
1666 'generate-password' => {
1670 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1675 additionalProperties
=> 0,
1677 user
=> { type
=> 'string' },
1678 ticket
=> { type
=> 'string' },
1681 description
=> "Returned if requested with 'generate-password' param."
1682 ." Consists of printable ASCII characters ('!' .. '~').",
1685 cert
=> { type
=> 'string' },
1686 port
=> { type
=> 'integer' },
1687 upid
=> { type
=> 'string' },
1693 my $rpcenv = PVE
::RPCEnvironment
::get
();
1695 my $authuser = $rpcenv->get_user();
1697 my $vmid = $param->{vmid
};
1698 my $node = $param->{node
};
1699 my $websocket = $param->{websocket
};
1701 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1705 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1706 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1709 my $authpath = "/vms/$vmid";
1711 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1712 my $password = $ticket;
1713 if ($param->{'generate-password'}) {
1714 $password = $gen_rand_chars->(8);
1717 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1723 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1724 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1725 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1726 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1727 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1729 $family = PVE
::Tools
::get_host_address_family
($node);
1732 my $port = PVE
::Tools
::next_vnc_port
($family);
1739 syslog
('info', "starting vnc proxy $upid\n");
1743 if (defined($serial)) {
1745 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1747 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1748 '-timeout', $timeout, '-authpath', $authpath,
1749 '-perm', 'Sys.Console'];
1751 if ($param->{websocket
}) {
1752 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1753 push @$cmd, '-notls', '-listen', 'localhost';
1756 push @$cmd, '-c', @$remcmd, @$termcmd;
1758 PVE
::Tools
::run_command
($cmd);
1762 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1764 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1766 my $sock = IO
::Socket
::IP-
>new(
1771 GetAddrInfoFlags
=> 0,
1772 ) or die "failed to create socket: $!\n";
1773 # Inside the worker we shouldn't have any previous alarms
1774 # running anyway...:
1776 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1778 accept(my $cli, $sock) or die "connection failed: $!\n";
1781 if (PVE
::Tools
::run_command
($cmd,
1782 output
=> '>&'.fileno($cli),
1783 input
=> '<&'.fileno($cli),
1786 die "Failed to run vncproxy.\n";
1793 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1795 PVE
::Tools
::wait_for_vnc_port
($port);
1804 $res->{password
} = $password if $param->{'generate-password'};
1809 __PACKAGE__-
>register_method({
1810 name
=> 'termproxy',
1811 path
=> '{vmid}/termproxy',
1815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1817 description
=> "Creates a TCP proxy connections.",
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid'),
1826 enum
=> [qw(serial0 serial1 serial2 serial3)],
1827 description
=> "opens a serial terminal (defaults to display)",
1832 additionalProperties
=> 0,
1834 user
=> { type
=> 'string' },
1835 ticket
=> { type
=> 'string' },
1836 port
=> { type
=> 'integer' },
1837 upid
=> { type
=> 'string' },
1843 my $rpcenv = PVE
::RPCEnvironment
::get
();
1845 my $authuser = $rpcenv->get_user();
1847 my $vmid = $param->{vmid
};
1848 my $node = $param->{node
};
1849 my $serial = $param->{serial
};
1851 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1853 if (!defined($serial)) {
1855 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1856 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1860 my $authpath = "/vms/$vmid";
1862 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1867 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1868 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1869 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1870 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1871 push @$remcmd, '--';
1873 $family = PVE
::Tools
::get_host_address_family
($node);
1876 my $port = PVE
::Tools
::next_vnc_port
($family);
1878 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1879 push @$termcmd, '-iface', $serial if $serial;
1884 syslog
('info', "starting qemu termproxy $upid\n");
1886 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1887 '--perm', 'VM.Console', '--'];
1888 push @$cmd, @$remcmd, @$termcmd;
1890 PVE
::Tools
::run_command
($cmd);
1893 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1895 PVE
::Tools
::wait_for_vnc_port
($port);
1905 __PACKAGE__-
>register_method({
1906 name
=> 'vncwebsocket',
1907 path
=> '{vmid}/vncwebsocket',
1910 description
=> "You also need to pass a valid ticket (vncticket).",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1913 description
=> "Opens a weksocket for VNC traffic.",
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1920 description
=> "Ticket from previous call to vncproxy.",
1925 description
=> "Port number returned by previous vncproxy call.",
1935 port
=> { type
=> 'string' },
1941 my $rpcenv = PVE
::RPCEnvironment
::get
();
1943 my $authuser = $rpcenv->get_user();
1945 my $vmid = $param->{vmid
};
1946 my $node = $param->{node
};
1948 my $authpath = "/vms/$vmid";
1950 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1952 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1954 # Note: VNC ports are acessible from outside, so we do not gain any
1955 # security if we verify that $param->{port} belongs to VM $vmid. This
1956 # check is done by verifying the VNC ticket (inside VNC protocol).
1958 my $port = $param->{port
};
1960 return { port
=> $port };
1963 __PACKAGE__-
>register_method({
1964 name
=> 'spiceproxy',
1965 path
=> '{vmid}/spiceproxy',
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1972 description
=> "Returns a SPICE configuration to connect to the VM.",
1974 additionalProperties
=> 0,
1976 node
=> get_standard_option
('pve-node'),
1977 vmid
=> get_standard_option
('pve-vmid'),
1978 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1981 returns
=> get_standard_option
('remote-viewer-config'),
1985 my $rpcenv = PVE
::RPCEnvironment
::get
();
1987 my $authuser = $rpcenv->get_user();
1989 my $vmid = $param->{vmid
};
1990 my $node = $param->{node
};
1991 my $proxy = $param->{proxy
};
1993 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1994 my $title = "VM $vmid";
1995 $title .= " - ". $conf->{name
} if $conf->{name
};
1997 my $port = PVE
::QemuServer
::spice_port
($vmid);
1999 my ($ticket, undef, $remote_viewer_config) =
2000 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2002 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2003 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2005 return $remote_viewer_config;
2008 __PACKAGE__-
>register_method({
2010 path
=> '{vmid}/status',
2013 description
=> "Directory index",
2018 additionalProperties
=> 0,
2020 node
=> get_standard_option
('pve-node'),
2021 vmid
=> get_standard_option
('pve-vmid'),
2029 subdir
=> { type
=> 'string' },
2032 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2038 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2041 { subdir
=> 'current' },
2042 { subdir
=> 'start' },
2043 { subdir
=> 'stop' },
2044 { subdir
=> 'reset' },
2045 { subdir
=> 'shutdown' },
2046 { subdir
=> 'suspend' },
2047 { subdir
=> 'reboot' },
2053 __PACKAGE__-
>register_method({
2054 name
=> 'vm_status',
2055 path
=> '{vmid}/status/current',
2058 protected
=> 1, # qemu pid files are only readable by root
2059 description
=> "Get virtual machine status.",
2061 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2064 additionalProperties
=> 0,
2066 node
=> get_standard_option
('pve-node'),
2067 vmid
=> get_standard_option
('pve-vmid'),
2073 %$PVE::QemuServer
::vmstatus_return_properties
,
2075 description
=> "HA manager service status.",
2079 description
=> "Qemu VGA configuration supports spice.",
2084 description
=> "Qemu GuestAgent enabled in config.",
2094 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2096 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2097 my $status = $vmstatus->{$param->{vmid
}};
2099 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2101 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2102 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2107 __PACKAGE__-
>register_method({
2109 path
=> '{vmid}/status/start',
2113 description
=> "Start virtual machine.",
2115 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2118 additionalProperties
=> 0,
2120 node
=> get_standard_option
('pve-node'),
2121 vmid
=> get_standard_option
('pve-vmid',
2122 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2123 skiplock
=> get_standard_option
('skiplock'),
2124 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2125 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2128 enum
=> ['secure', 'insecure'],
2129 description
=> "Migration traffic is encrypted using an SSH " .
2130 "tunnel by default. On secure, completely private networks " .
2131 "this can be disabled to increase performance.",
2134 migration_network
=> {
2135 type
=> 'string', format
=> 'CIDR',
2136 description
=> "CIDR of the (sub) network that is used for migration.",
2139 machine
=> get_standard_option
('pve-qemu-machine'),
2141 description
=> "Override QEMU's -cpu argument with the given string.",
2145 targetstorage
=> get_standard_option
('pve-targetstorage'),
2147 description
=> "Wait maximal timeout seconds.",
2150 default => 'max(30, vm memory in GiB)',
2161 my $rpcenv = PVE
::RPCEnvironment
::get
();
2162 my $authuser = $rpcenv->get_user();
2164 my $node = extract_param
($param, 'node');
2165 my $vmid = extract_param
($param, 'vmid');
2166 my $timeout = extract_param
($param, 'timeout');
2168 my $machine = extract_param
($param, 'machine');
2169 my $force_cpu = extract_param
($param, 'force-cpu');
2171 my $get_root_param = sub {
2172 my $value = extract_param
($param, $_[0]);
2173 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2174 if $value && $authuser ne 'root@pam';
2178 my $stateuri = $get_root_param->('stateuri');
2179 my $skiplock = $get_root_param->('skiplock');
2180 my $migratedfrom = $get_root_param->('migratedfrom');
2181 my $migration_type = $get_root_param->('migration_type');
2182 my $migration_network = $get_root_param->('migration_network');
2183 my $targetstorage = $get_root_param->('targetstorage');
2187 if ($targetstorage) {
2188 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2190 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2191 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2195 # read spice ticket from STDIN
2197 my $nbd_protocol_version = 0;
2198 my $replicated_volumes = {};
2199 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2200 while (defined(my $line = <STDIN
>)) {
2202 if ($line =~ m/^spice_ticket: (.+)$/) {
2204 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2205 $nbd_protocol_version = $1;
2206 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2207 $replicated_volumes->{$1} = 1;
2209 # fallback for old source node
2210 $spice_ticket = $line;
2215 PVE
::Cluster
::check_cfs_quorum
();
2217 my $storecfg = PVE
::Storage
::config
();
2219 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2223 print "Requesting HA start for VM $vmid\n";
2225 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2226 PVE
::Tools
::run_command
($cmd);
2230 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2237 syslog
('info', "start VM $vmid: $upid\n");
2239 my $migrate_opts = {
2240 migratedfrom
=> $migratedfrom,
2241 spice_ticket
=> $spice_ticket,
2242 network
=> $migration_network,
2243 type
=> $migration_type,
2244 storagemap
=> $storagemap,
2245 nbd_proto_version
=> $nbd_protocol_version,
2246 replicated_volumes
=> $replicated_volumes,
2250 statefile
=> $stateuri,
2251 skiplock
=> $skiplock,
2252 forcemachine
=> $machine,
2253 timeout
=> $timeout,
2254 forcecpu
=> $force_cpu,
2257 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2261 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2265 __PACKAGE__-
>register_method({
2267 path
=> '{vmid}/status/stop',
2271 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2272 "is akin to pulling the power plug of a running computer and may damage the VM data",
2274 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2277 additionalProperties
=> 0,
2279 node
=> get_standard_option
('pve-node'),
2280 vmid
=> get_standard_option
('pve-vmid',
2281 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2282 skiplock
=> get_standard_option
('skiplock'),
2283 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2285 description
=> "Wait maximal timeout seconds.",
2291 description
=> "Do not deactivate storage volumes.",
2304 my $rpcenv = PVE
::RPCEnvironment
::get
();
2305 my $authuser = $rpcenv->get_user();
2307 my $node = extract_param
($param, 'node');
2308 my $vmid = extract_param
($param, 'vmid');
2310 my $skiplock = extract_param
($param, 'skiplock');
2311 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2312 if $skiplock && $authuser ne 'root@pam';
2314 my $keepActive = extract_param
($param, 'keepActive');
2315 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2316 if $keepActive && $authuser ne 'root@pam';
2318 my $migratedfrom = extract_param
($param, 'migratedfrom');
2319 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2320 if $migratedfrom && $authuser ne 'root@pam';
2323 my $storecfg = PVE
::Storage
::config
();
2325 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2330 print "Requesting HA stop for VM $vmid\n";
2332 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2333 PVE
::Tools
::run_command
($cmd);
2337 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2343 syslog
('info', "stop VM $vmid: $upid\n");
2345 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2346 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2350 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2354 __PACKAGE__-
>register_method({
2356 path
=> '{vmid}/status/reset',
2360 description
=> "Reset virtual machine.",
2362 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2365 additionalProperties
=> 0,
2367 node
=> get_standard_option
('pve-node'),
2368 vmid
=> get_standard_option
('pve-vmid',
2369 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2370 skiplock
=> get_standard_option
('skiplock'),
2379 my $rpcenv = PVE
::RPCEnvironment
::get
();
2381 my $authuser = $rpcenv->get_user();
2383 my $node = extract_param
($param, 'node');
2385 my $vmid = extract_param
($param, 'vmid');
2387 my $skiplock = extract_param
($param, 'skiplock');
2388 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2389 if $skiplock && $authuser ne 'root@pam';
2391 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2396 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2401 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2404 my sub vm_is_paused
{
2406 my $qmpstatus = eval {
2407 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2408 mon_cmd
($vmid, "query-status");
2411 return $qmpstatus && $qmpstatus->{status
} eq "paused";
2414 __PACKAGE__-
>register_method({
2415 name
=> 'vm_shutdown',
2416 path
=> '{vmid}/status/shutdown',
2420 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2421 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2423 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2426 additionalProperties
=> 0,
2428 node
=> get_standard_option
('pve-node'),
2429 vmid
=> get_standard_option
('pve-vmid',
2430 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2431 skiplock
=> get_standard_option
('skiplock'),
2433 description
=> "Wait maximal timeout seconds.",
2439 description
=> "Make sure the VM stops.",
2445 description
=> "Do not deactivate storage volumes.",
2458 my $rpcenv = PVE
::RPCEnvironment
::get
();
2459 my $authuser = $rpcenv->get_user();
2461 my $node = extract_param
($param, 'node');
2462 my $vmid = extract_param
($param, 'vmid');
2464 my $skiplock = extract_param
($param, 'skiplock');
2465 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2466 if $skiplock && $authuser ne 'root@pam';
2468 my $keepActive = extract_param
($param, 'keepActive');
2469 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2470 if $keepActive && $authuser ne 'root@pam';
2472 my $storecfg = PVE
::Storage
::config
();
2476 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2477 # otherwise, we will infer a shutdown command, but run into the timeout,
2478 # then when the vm is resumed, it will instantly shutdown
2480 # checking the qmp status here to get feedback to the gui/cli/api
2481 # and the status query should not take too long
2482 if (vm_is_paused
($vmid)) {
2483 if ($param->{forceStop
}) {
2484 warn "VM is paused - stop instead of shutdown\n";
2487 die "VM is paused - cannot shutdown\n";
2491 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2493 my $timeout = $param->{timeout
} // 60;
2497 print "Requesting HA stop for VM $vmid\n";
2499 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2500 PVE
::Tools
::run_command
($cmd);
2504 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2511 syslog
('info', "shutdown VM $vmid: $upid\n");
2513 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2514 $shutdown, $param->{forceStop
}, $keepActive);
2518 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2522 __PACKAGE__-
>register_method({
2523 name
=> 'vm_reboot',
2524 path
=> '{vmid}/status/reboot',
2528 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2530 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2533 additionalProperties
=> 0,
2535 node
=> get_standard_option
('pve-node'),
2536 vmid
=> get_standard_option
('pve-vmid',
2537 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2539 description
=> "Wait maximal timeout seconds for the shutdown.",
2552 my $rpcenv = PVE
::RPCEnvironment
::get
();
2553 my $authuser = $rpcenv->get_user();
2555 my $node = extract_param
($param, 'node');
2556 my $vmid = extract_param
($param, 'vmid');
2558 die "VM is paused - cannot shutdown\n" if vm_is_paused
($vmid);
2560 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2565 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2566 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2570 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2573 __PACKAGE__-
>register_method({
2574 name
=> 'vm_suspend',
2575 path
=> '{vmid}/status/suspend',
2579 description
=> "Suspend virtual machine.",
2581 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2582 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2583 " on the storage for the vmstate.",
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2587 additionalProperties
=> 0,
2589 node
=> get_standard_option
('pve-node'),
2590 vmid
=> get_standard_option
('pve-vmid',
2591 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2592 skiplock
=> get_standard_option
('skiplock'),
2597 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2599 statestorage
=> get_standard_option
('pve-storage-id', {
2600 description
=> "The storage for the VM state",
2601 requires
=> 'todisk',
2603 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2613 my $rpcenv = PVE
::RPCEnvironment
::get
();
2614 my $authuser = $rpcenv->get_user();
2616 my $node = extract_param
($param, 'node');
2617 my $vmid = extract_param
($param, 'vmid');
2619 my $todisk = extract_param
($param, 'todisk') // 0;
2621 my $statestorage = extract_param
($param, 'statestorage');
2623 my $skiplock = extract_param
($param, 'skiplock');
2624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2625 if $skiplock && $authuser ne 'root@pam';
2627 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2629 die "Cannot suspend HA managed VM to disk\n"
2630 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2632 # early check for storage permission, for better user feedback
2634 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2636 if (!$statestorage) {
2637 # get statestorage from config if none is given
2638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2639 my $storecfg = PVE
::Storage
::config
();
2640 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2643 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2649 syslog
('info', "suspend VM $vmid: $upid\n");
2651 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2656 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2657 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2660 __PACKAGE__-
>register_method({
2661 name
=> 'vm_resume',
2662 path
=> '{vmid}/status/resume',
2666 description
=> "Resume virtual machine.",
2668 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid',
2675 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2676 skiplock
=> get_standard_option
('skiplock'),
2677 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2687 my $rpcenv = PVE
::RPCEnvironment
::get
();
2689 my $authuser = $rpcenv->get_user();
2691 my $node = extract_param
($param, 'node');
2693 my $vmid = extract_param
($param, 'vmid');
2695 my $skiplock = extract_param
($param, 'skiplock');
2696 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2697 if $skiplock && $authuser ne 'root@pam';
2699 my $nocheck = extract_param
($param, 'nocheck');
2700 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2701 if $nocheck && $authuser ne 'root@pam';
2703 my $to_disk_suspended;
2705 PVE
::QemuConfig-
>lock_config($vmid, sub {
2706 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2707 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2711 die "VM $vmid not running\n"
2712 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2717 syslog
('info', "resume VM $vmid: $upid\n");
2719 if (!$to_disk_suspended) {
2720 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2722 my $storecfg = PVE
::Storage
::config
();
2723 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2729 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2732 __PACKAGE__-
>register_method({
2733 name
=> 'vm_sendkey',
2734 path
=> '{vmid}/sendkey',
2738 description
=> "Send key event to virtual machine.",
2740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2743 additionalProperties
=> 0,
2745 node
=> get_standard_option
('pve-node'),
2746 vmid
=> get_standard_option
('pve-vmid',
2747 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2748 skiplock
=> get_standard_option
('skiplock'),
2750 description
=> "The key (qemu monitor encoding).",
2755 returns
=> { type
=> 'null'},
2759 my $rpcenv = PVE
::RPCEnvironment
::get
();
2761 my $authuser = $rpcenv->get_user();
2763 my $node = extract_param
($param, 'node');
2765 my $vmid = extract_param
($param, 'vmid');
2767 my $skiplock = extract_param
($param, 'skiplock');
2768 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2769 if $skiplock && $authuser ne 'root@pam';
2771 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2776 __PACKAGE__-
>register_method({
2777 name
=> 'vm_feature',
2778 path
=> '{vmid}/feature',
2782 description
=> "Check if feature for virtual machine is available.",
2784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2787 additionalProperties
=> 0,
2789 node
=> get_standard_option
('pve-node'),
2790 vmid
=> get_standard_option
('pve-vmid'),
2792 description
=> "Feature to check.",
2794 enum
=> [ 'snapshot', 'clone', 'copy' ],
2796 snapname
=> get_standard_option
('pve-snapshot-name', {
2804 hasFeature
=> { type
=> 'boolean' },
2807 items
=> { type
=> 'string' },
2814 my $node = extract_param
($param, 'node');
2816 my $vmid = extract_param
($param, 'vmid');
2818 my $snapname = extract_param
($param, 'snapname');
2820 my $feature = extract_param
($param, 'feature');
2822 my $running = PVE
::QemuServer
::check_running
($vmid);
2824 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2827 my $snap = $conf->{snapshots
}->{$snapname};
2828 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2831 my $storecfg = PVE
::Storage
::config
();
2833 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2834 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2837 hasFeature
=> $hasFeature,
2838 nodes
=> [ keys %$nodelist ],
2842 __PACKAGE__-
>register_method({
2844 path
=> '{vmid}/clone',
2848 description
=> "Create a copy of virtual machine/template.",
2850 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2851 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2852 "'Datastore.AllocateSpace' on any used storage.",
2855 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2857 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2858 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2867 newid
=> get_standard_option
('pve-vmid', {
2868 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2869 description
=> 'VMID for the clone.' }),
2872 type
=> 'string', format
=> 'dns-name',
2873 description
=> "Set a name for the new VM.",
2878 description
=> "Description for the new VM.",
2882 type
=> 'string', format
=> 'pve-poolid',
2883 description
=> "Add the new VM to the specified pool.",
2885 snapname
=> get_standard_option
('pve-snapshot-name', {
2888 storage
=> get_standard_option
('pve-storage-id', {
2889 description
=> "Target storage for full clone.",
2893 description
=> "Target format for file storage. Only valid for full clone.",
2896 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2901 description
=> "Create a full copy of all disks. This is always done when " .
2902 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2904 target
=> get_standard_option
('pve-node', {
2905 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2909 description
=> "Override I/O bandwidth limit (in KiB/s).",
2913 default => 'clone limit from datacenter or storage config',
2923 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2926 my $node = extract_param
($param, 'node');
2927 my $vmid = extract_param
($param, 'vmid');
2928 my $newid = extract_param
($param, 'newid');
2929 my $pool = extract_param
($param, 'pool');
2930 $rpcenv->check_pool_exist($pool) if defined($pool);
2932 my $snapname = extract_param
($param, 'snapname');
2933 my $storage = extract_param
($param, 'storage');
2934 my $format = extract_param
($param, 'format');
2935 my $target = extract_param
($param, 'target');
2937 my $localnode = PVE
::INotify
::nodename
();
2939 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2943 PVE
::Cluster
::check_node_exists
($target) if $target;
2945 my $storecfg = PVE
::Storage
::config
();
2948 # check if storage is enabled on local node
2949 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2951 # check if storage is available on target node
2952 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2953 # clone only works if target storage is shared
2954 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2955 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2959 PVE
::Cluster
::check_cfs_quorum
();
2961 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2964 # do all tests after lock but before forking worker - if possible
2966 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2967 PVE
::QemuConfig-
>check_lock($conf);
2969 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2970 die "unexpected state change\n" if $verify_running != $running;
2972 die "snapshot '$snapname' does not exist\n"
2973 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2975 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2977 die "parameter 'storage' not allowed for linked clones\n"
2978 if defined($storage) && !$full;
2980 die "parameter 'format' not allowed for linked clones\n"
2981 if defined($format) && !$full;
2983 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2985 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2987 die "can't clone VM to node '$target' (VM uses local storage)\n"
2988 if $target && !$sharedvm;
2990 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2991 die "unable to create VM $newid: config file already exists\n"
2994 my $newconf = { lock => 'clone' };
2999 foreach my $opt (keys %$oldconf) {
3000 my $value = $oldconf->{$opt};
3002 # do not copy snapshot related info
3003 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3004 $opt eq 'vmstate' || $opt eq 'snapstate';
3006 # no need to copy unused images, because VMID(owner) changes anyways
3007 next if $opt =~ m/^unused\d+$/;
3009 # always change MAC! address
3010 if ($opt =~ m/^net(\d+)$/) {
3011 my $net = PVE
::QemuServer
::parse_net
($value);
3012 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3013 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3014 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3015 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3016 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3017 die "unable to parse drive options for '$opt'\n" if !$drive;
3018 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3019 $newconf->{$opt} = $value; # simply copy configuration
3021 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3022 die "Full clone feature is not supported for drive '$opt'\n"
3023 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3024 $fullclone->{$opt} = 1;
3026 # not full means clone instead of copy
3027 die "Linked clone feature is not supported for drive '$opt'\n"
3028 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3030 $drives->{$opt} = $drive;
3031 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3032 push @$vollist, $drive->{file
};
3035 # copy everything else
3036 $newconf->{$opt} = $value;
3040 # auto generate a new uuid
3041 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3042 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3043 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3044 # auto generate a new vmgenid only if the option was set for template
3045 if ($newconf->{vmgenid
}) {
3046 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3049 delete $newconf->{template
};
3051 if ($param->{name
}) {
3052 $newconf->{name
} = $param->{name
};
3054 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3057 if ($param->{description
}) {
3058 $newconf->{description
} = $param->{description
};
3061 # create empty/temp config - this fails if VM already exists on other node
3062 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3063 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3068 my $newvollist = [];
3075 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3077 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3079 my $bwlimit = extract_param
($param, 'bwlimit');
3081 my $total_jobs = scalar(keys %{$drives});
3084 foreach my $opt (keys %$drives) {
3085 my $drive = $drives->{$opt};
3086 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3087 my $completion = $skipcomplete ?
'skip' : 'complete';
3089 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3090 my $storage_list = [ $src_sid ];
3091 push @$storage_list, $storage if defined($storage);
3092 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3094 my $newdrive = PVE
::QemuServer
::clone_disk
(
3113 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3115 PVE
::QemuConfig-
>write_config($newid, $newconf);
3119 delete $newconf->{lock};
3121 # do not write pending changes
3122 if (my @changes = keys %{$newconf->{pending
}}) {
3123 my $pending = join(',', @changes);
3124 warn "found pending changes for '$pending', discarding for clone\n";
3125 delete $newconf->{pending
};
3128 PVE
::QemuConfig-
>write_config($newid, $newconf);
3131 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3132 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3133 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3135 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3136 die "Failed to move config to node '$target' - rename failed: $!\n"
3137 if !rename($conffile, $newconffile);
3140 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3143 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3144 sleep 1; # some storage like rbd need to wait before release volume - really?
3146 foreach my $volid (@$newvollist) {
3147 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3151 PVE
::Firewall
::remove_vmfw_conf
($newid);
3153 unlink $conffile; # avoid races -> last thing before die
3155 die "clone failed: $err";
3161 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3163 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3166 # Aquire exclusive lock lock for $newid
3167 my $lock_target_vm = sub {
3168 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3171 # exclusive lock if VM is running - else shared lock is enough;
3173 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3175 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3179 __PACKAGE__-
>register_method({
3180 name
=> 'move_vm_disk',
3181 path
=> '{vmid}/move_disk',
3185 description
=> "Move volume to different storage.",
3187 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3189 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3190 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3194 additionalProperties
=> 0,
3196 node
=> get_standard_option
('pve-node'),
3197 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3200 description
=> "The disk you want to move.",
3201 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3203 storage
=> get_standard_option
('pve-storage-id', {
3204 description
=> "Target storage.",
3205 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3209 description
=> "Target Format.",
3210 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3215 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3221 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3226 description
=> "Override I/O bandwidth limit (in KiB/s).",
3230 default => 'move limit from datacenter or storage config',
3236 description
=> "the task ID.",
3241 my $rpcenv = PVE
::RPCEnvironment
::get
();
3242 my $authuser = $rpcenv->get_user();
3244 my $node = extract_param
($param, 'node');
3245 my $vmid = extract_param
($param, 'vmid');
3246 my $digest = extract_param
($param, 'digest');
3247 my $disk = extract_param
($param, 'disk');
3248 my $storeid = extract_param
($param, 'storage');
3249 my $format = extract_param
($param, 'format');
3251 my $storecfg = PVE
::Storage
::config
();
3253 my $updatefn = sub {
3254 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3255 PVE
::QemuConfig-
>check_lock($conf);
3257 die "VM config checksum missmatch (file change by other user?)\n"
3258 if $digest && $digest ne $conf->{digest
};
3260 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3262 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3264 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3265 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3267 my $old_volid = $drive->{file
};
3269 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3270 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3274 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3275 (!$format || !$oldfmt || $oldfmt eq $format);
3277 # this only checks snapshots because $disk is passed!
3278 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3279 die "you can't move a disk with snapshots and delete the source\n"
3280 if $snapshotted && $param->{delete};
3282 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3284 my $running = PVE
::QemuServer
::check_running
($vmid);
3286 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3289 my $newvollist = [];
3295 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3297 warn "moving disk with snapshots, snapshots will not be moved!\n"
3300 my $bwlimit = extract_param
($param, 'bwlimit');
3301 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3303 my $newdrive = PVE
::QemuServer
::clone_disk
(
3321 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3323 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3325 # convert moved disk to base if part of template
3326 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3327 if PVE
::QemuConfig-
>is_template($conf);
3329 PVE
::QemuConfig-
>write_config($vmid, $conf);
3331 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3332 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3333 eval { mon_cmd
($vmid, "guest-fstrim") };
3337 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3338 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3344 foreach my $volid (@$newvollist) {
3345 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3348 die "storage migration failed: $err";
3351 if ($param->{delete}) {
3353 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3354 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3360 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3363 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3366 my $check_vm_disks_local = sub {
3367 my ($storecfg, $vmconf, $vmid) = @_;
3369 my $local_disks = {};
3371 # add some more information to the disks e.g. cdrom
3372 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3373 my ($volid, $attr) = @_;
3375 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3377 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3378 return if $scfg->{shared
};
3380 # The shared attr here is just a special case where the vdisk
3381 # is marked as shared manually
3382 return if $attr->{shared
};
3383 return if $attr->{cdrom
} and $volid eq "none";
3385 if (exists $local_disks->{$volid}) {
3386 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3388 $local_disks->{$volid} = $attr;
3389 # ensure volid is present in case it's needed
3390 $local_disks->{$volid}->{volid
} = $volid;
3394 return $local_disks;
3397 __PACKAGE__-
>register_method({
3398 name
=> 'migrate_vm_precondition',
3399 path
=> '{vmid}/migrate',
3403 description
=> "Get preconditions for migration.",
3405 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3408 additionalProperties
=> 0,
3410 node
=> get_standard_option
('pve-node'),
3411 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3412 target
=> get_standard_option
('pve-node', {
3413 description
=> "Target node.",
3414 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3422 running
=> { type
=> 'boolean' },
3426 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3428 not_allowed_nodes
=> {
3431 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3435 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3437 local_resources
=> {
3439 description
=> "List local resources e.g. pci, usb"
3446 my $rpcenv = PVE
::RPCEnvironment
::get
();
3448 my $authuser = $rpcenv->get_user();
3450 PVE
::Cluster
::check_cfs_quorum
();
3454 my $vmid = extract_param
($param, 'vmid');
3455 my $target = extract_param
($param, 'target');
3456 my $localnode = PVE
::INotify
::nodename
();
3460 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3461 my $storecfg = PVE
::Storage
::config
();
3464 # try to detect errors early
3465 PVE
::QemuConfig-
>check_lock($vmconf);
3467 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3469 # if vm is not running, return target nodes where local storage is available
3470 # for offline migration
3471 if (!$res->{running
}) {
3472 $res->{allowed_nodes
} = [];
3473 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3474 delete $checked_nodes->{$localnode};
3476 foreach my $node (keys %$checked_nodes) {
3477 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3478 push @{$res->{allowed_nodes
}}, $node;
3482 $res->{not_allowed_nodes
} = $checked_nodes;
3486 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3487 $res->{local_disks
} = [ values %$local_disks ];;
3489 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3491 $res->{local_resources
} = $local_resources;
3498 __PACKAGE__-
>register_method({
3499 name
=> 'migrate_vm',
3500 path
=> '{vmid}/migrate',
3504 description
=> "Migrate virtual machine. Creates a new migration task.",
3506 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3509 additionalProperties
=> 0,
3511 node
=> get_standard_option
('pve-node'),
3512 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3513 target
=> get_standard_option
('pve-node', {
3514 description
=> "Target node.",
3515 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3519 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3524 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3529 enum
=> ['secure', 'insecure'],
3530 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3533 migration_network
=> {
3534 type
=> 'string', format
=> 'CIDR',
3535 description
=> "CIDR of the (sub) network that is used for migration.",
3538 "with-local-disks" => {
3540 description
=> "Enable live storage migration for local disk",
3543 targetstorage
=> get_standard_option
('pve-targetstorage', {
3544 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3547 description
=> "Override I/O bandwidth limit (in KiB/s).",
3551 default => 'migrate limit from datacenter or storage config',
3557 description
=> "the task ID.",
3562 my $rpcenv = PVE
::RPCEnvironment
::get
();
3563 my $authuser = $rpcenv->get_user();
3565 my $target = extract_param
($param, 'target');
3567 my $localnode = PVE
::INotify
::nodename
();
3568 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3570 PVE
::Cluster
::check_cfs_quorum
();
3572 PVE
::Cluster
::check_node_exists
($target);
3574 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3576 my $vmid = extract_param
($param, 'vmid');
3578 raise_param_exc
({ force
=> "Only root may use this option." })
3579 if $param->{force
} && $authuser ne 'root@pam';
3581 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3582 if $param->{migration_type
} && $authuser ne 'root@pam';
3584 # allow root only until better network permissions are available
3585 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3586 if $param->{migration_network
} && $authuser ne 'root@pam';
3589 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3591 # try to detect errors early
3593 PVE
::QemuConfig-
>check_lock($conf);
3595 if (PVE
::QemuServer
::check_running
($vmid)) {
3596 die "can't migrate running VM without --online\n" if !$param->{online
};
3598 my $repl_conf = PVE
::ReplicationConfig-
>new();
3599 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3600 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3601 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3602 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3603 "target. Use 'force' to override.\n";
3606 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3607 $param->{online
} = 0;
3610 my $storecfg = PVE
::Storage
::config
();
3612 if (my $targetstorage = $param->{targetstorage
}) {
3613 my $check_storage = sub {
3614 my ($target_sid) = @_;
3615 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3616 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3617 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3618 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3619 if !$scfg->{content
}->{images
};
3622 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3623 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3626 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3627 if !defined($storagemap->{identity
});
3629 foreach my $source (values %{$storagemap->{entries
}}) {
3630 $check_storage->($source);
3633 $check_storage->($storagemap->{default})
3634 if $storagemap->{default};
3636 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3637 if $storagemap->{identity
};
3639 $param->{storagemap
} = $storagemap;
3641 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3644 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3649 print "Requesting HA migration for VM $vmid to node $target\n";
3651 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3652 PVE
::Tools
::run_command
($cmd);
3656 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3661 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3665 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3668 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3673 __PACKAGE__-
>register_method({
3675 path
=> '{vmid}/monitor',
3679 description
=> "Execute Qemu monitor commands.",
3681 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3682 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3685 additionalProperties
=> 0,
3687 node
=> get_standard_option
('pve-node'),
3688 vmid
=> get_standard_option
('pve-vmid'),
3691 description
=> "The monitor command.",
3695 returns
=> { type
=> 'string'},
3699 my $rpcenv = PVE
::RPCEnvironment
::get
();
3700 my $authuser = $rpcenv->get_user();
3703 my $command = shift;
3704 return $command =~ m/^\s*info(\s+|$)/
3705 || $command =~ m/^\s*help\s*$/;
3708 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3709 if !&$is_ro($param->{command
});
3711 my $vmid = $param->{vmid
};
3713 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3717 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3719 $res = "ERROR: $@" if $@;
3724 __PACKAGE__-
>register_method({
3725 name
=> 'resize_vm',
3726 path
=> '{vmid}/resize',
3730 description
=> "Extend volume size.",
3732 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3735 additionalProperties
=> 0,
3737 node
=> get_standard_option
('pve-node'),
3738 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3739 skiplock
=> get_standard_option
('skiplock'),
3742 description
=> "The disk you want to resize.",
3743 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3747 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3748 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.",
3752 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3758 returns
=> { type
=> 'null'},
3762 my $rpcenv = PVE
::RPCEnvironment
::get
();
3764 my $authuser = $rpcenv->get_user();
3766 my $node = extract_param
($param, 'node');
3768 my $vmid = extract_param
($param, 'vmid');
3770 my $digest = extract_param
($param, 'digest');
3772 my $disk = extract_param
($param, 'disk');
3774 my $sizestr = extract_param
($param, 'size');
3776 my $skiplock = extract_param
($param, 'skiplock');
3777 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3778 if $skiplock && $authuser ne 'root@pam';
3780 my $storecfg = PVE
::Storage
::config
();
3782 my $updatefn = sub {
3784 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3786 die "checksum missmatch (file change by other user?)\n"
3787 if $digest && $digest ne $conf->{digest
};
3788 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3790 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3792 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3794 my (undef, undef, undef, undef, undef, undef, $format) =
3795 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3797 die "can't resize volume: $disk if snapshot exists\n"
3798 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3800 my $volid = $drive->{file
};
3802 die "disk '$disk' has no associated volume\n" if !$volid;
3804 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3806 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3808 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3810 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3811 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3813 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3815 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3816 my ($ext, $newsize, $unit) = ($1, $2, $4);
3819 $newsize = $newsize * 1024;
3820 } elsif ($unit eq 'M') {
3821 $newsize = $newsize * 1024 * 1024;
3822 } elsif ($unit eq 'G') {
3823 $newsize = $newsize * 1024 * 1024 * 1024;
3824 } elsif ($unit eq 'T') {
3825 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3828 $newsize += $size if $ext;
3829 $newsize = int($newsize);
3831 die "shrinking disks is not supported\n" if $newsize < $size;
3833 return if $size == $newsize;
3835 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3837 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3839 $drive->{size
} = $newsize;
3840 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3842 PVE
::QemuConfig-
>write_config($vmid, $conf);
3845 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3849 __PACKAGE__-
>register_method({
3850 name
=> 'snapshot_list',
3851 path
=> '{vmid}/snapshot',
3853 description
=> "List all snapshots.",
3855 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3858 protected
=> 1, # qemu pid files are only readable by root
3860 additionalProperties
=> 0,
3862 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3863 node
=> get_standard_option
('pve-node'),
3872 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3876 description
=> "Snapshot includes RAM.",
3881 description
=> "Snapshot description.",
3885 description
=> "Snapshot creation time",
3887 renderer
=> 'timestamp',
3891 description
=> "Parent snapshot identifier.",
3897 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3902 my $vmid = $param->{vmid
};
3904 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3905 my $snaphash = $conf->{snapshots
} || {};
3909 foreach my $name (keys %$snaphash) {
3910 my $d = $snaphash->{$name};
3913 snaptime
=> $d->{snaptime
} || 0,
3914 vmstate
=> $d->{vmstate
} ?
1 : 0,
3915 description
=> $d->{description
} || '',
3917 $item->{parent
} = $d->{parent
} if $d->{parent
};
3918 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3922 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3925 digest
=> $conf->{digest
},
3926 running
=> $running,
3927 description
=> "You are here!",
3929 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3931 push @$res, $current;
3936 __PACKAGE__-
>register_method({
3938 path
=> '{vmid}/snapshot',
3942 description
=> "Snapshot a VM.",
3944 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3947 additionalProperties
=> 0,
3949 node
=> get_standard_option
('pve-node'),
3950 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3951 snapname
=> get_standard_option
('pve-snapshot-name'),
3955 description
=> "Save the vmstate",
3960 description
=> "A textual description or comment.",
3966 description
=> "the task ID.",
3971 my $rpcenv = PVE
::RPCEnvironment
::get
();
3973 my $authuser = $rpcenv->get_user();
3975 my $node = extract_param
($param, 'node');
3977 my $vmid = extract_param
($param, 'vmid');
3979 my $snapname = extract_param
($param, 'snapname');
3981 die "unable to use snapshot name 'current' (reserved name)\n"
3982 if $snapname eq 'current';
3984 die "unable to use snapshot name 'pending' (reserved name)\n"
3985 if lc($snapname) eq 'pending';
3988 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3989 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3990 $param->{description
});
3993 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3996 __PACKAGE__-
>register_method({
3997 name
=> 'snapshot_cmd_idx',
3998 path
=> '{vmid}/snapshot/{snapname}',
4005 additionalProperties
=> 0,
4007 vmid
=> get_standard_option
('pve-vmid'),
4008 node
=> get_standard_option
('pve-node'),
4009 snapname
=> get_standard_option
('pve-snapshot-name'),
4018 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4025 push @$res, { cmd
=> 'rollback' };
4026 push @$res, { cmd
=> 'config' };
4031 __PACKAGE__-
>register_method({
4032 name
=> 'update_snapshot_config',
4033 path
=> '{vmid}/snapshot/{snapname}/config',
4037 description
=> "Update snapshot metadata.",
4039 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4042 additionalProperties
=> 0,
4044 node
=> get_standard_option
('pve-node'),
4045 vmid
=> get_standard_option
('pve-vmid'),
4046 snapname
=> get_standard_option
('pve-snapshot-name'),
4050 description
=> "A textual description or comment.",
4054 returns
=> { type
=> 'null' },
4058 my $rpcenv = PVE
::RPCEnvironment
::get
();
4060 my $authuser = $rpcenv->get_user();
4062 my $vmid = extract_param
($param, 'vmid');
4064 my $snapname = extract_param
($param, 'snapname');
4066 return if !defined($param->{description
});
4068 my $updatefn = sub {
4070 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4072 PVE
::QemuConfig-
>check_lock($conf);
4074 my $snap = $conf->{snapshots
}->{$snapname};
4076 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4078 $snap->{description
} = $param->{description
} if defined($param->{description
});
4080 PVE
::QemuConfig-
>write_config($vmid, $conf);
4083 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4088 __PACKAGE__-
>register_method({
4089 name
=> 'get_snapshot_config',
4090 path
=> '{vmid}/snapshot/{snapname}/config',
4093 description
=> "Get snapshot configuration",
4095 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4098 additionalProperties
=> 0,
4100 node
=> get_standard_option
('pve-node'),
4101 vmid
=> get_standard_option
('pve-vmid'),
4102 snapname
=> get_standard_option
('pve-snapshot-name'),
4105 returns
=> { type
=> "object" },
4109 my $rpcenv = PVE
::RPCEnvironment
::get
();
4111 my $authuser = $rpcenv->get_user();
4113 my $vmid = extract_param
($param, 'vmid');
4115 my $snapname = extract_param
($param, 'snapname');
4117 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4119 my $snap = $conf->{snapshots
}->{$snapname};
4121 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4126 __PACKAGE__-
>register_method({
4128 path
=> '{vmid}/snapshot/{snapname}/rollback',
4132 description
=> "Rollback VM state to specified snapshot.",
4134 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4137 additionalProperties
=> 0,
4139 node
=> get_standard_option
('pve-node'),
4140 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4141 snapname
=> get_standard_option
('pve-snapshot-name'),
4146 description
=> "the task ID.",
4151 my $rpcenv = PVE
::RPCEnvironment
::get
();
4153 my $authuser = $rpcenv->get_user();
4155 my $node = extract_param
($param, 'node');
4157 my $vmid = extract_param
($param, 'vmid');
4159 my $snapname = extract_param
($param, 'snapname');
4162 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4163 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4167 # hold migration lock, this makes sure that nobody create replication snapshots
4168 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4171 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4174 __PACKAGE__-
>register_method({
4175 name
=> 'delsnapshot',
4176 path
=> '{vmid}/snapshot/{snapname}',
4180 description
=> "Delete a VM snapshot.",
4182 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4185 additionalProperties
=> 0,
4187 node
=> get_standard_option
('pve-node'),
4188 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4189 snapname
=> get_standard_option
('pve-snapshot-name'),
4193 description
=> "For removal from config file, even if removing disk snapshots fails.",
4199 description
=> "the task ID.",
4204 my $rpcenv = PVE
::RPCEnvironment
::get
();
4206 my $authuser = $rpcenv->get_user();
4208 my $node = extract_param
($param, 'node');
4210 my $vmid = extract_param
($param, 'vmid');
4212 my $snapname = extract_param
($param, 'snapname');
4215 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4216 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4219 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4222 __PACKAGE__-
>register_method({
4224 path
=> '{vmid}/template',
4228 description
=> "Create a Template.",
4230 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4231 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4234 additionalProperties
=> 0,
4236 node
=> get_standard_option
('pve-node'),
4237 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4241 description
=> "If you want to convert only 1 disk to base image.",
4242 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4247 returns
=> { type
=> 'null'},
4251 my $rpcenv = PVE
::RPCEnvironment
::get
();
4253 my $authuser = $rpcenv->get_user();
4255 my $node = extract_param
($param, 'node');
4257 my $vmid = extract_param
($param, 'vmid');
4259 my $disk = extract_param
($param, 'disk');
4261 my $updatefn = sub {
4263 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4265 PVE
::QemuConfig-
>check_lock($conf);
4267 die "unable to create template, because VM contains snapshots\n"
4268 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4270 die "you can't convert a template to a template\n"
4271 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4273 die "you can't convert a VM to template if VM is running\n"
4274 if PVE
::QemuServer
::check_running
($vmid);
4277 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4280 $conf->{template
} = 1;
4281 PVE
::QemuConfig-
>write_config($vmid, $conf);
4283 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4286 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4290 __PACKAGE__-
>register_method({
4291 name
=> 'cloudinit_generated_config_dump',
4292 path
=> '{vmid}/cloudinit/dump',
4295 description
=> "Get automatically generated cloudinit config.",
4297 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4300 additionalProperties
=> 0,
4302 node
=> get_standard_option
('pve-node'),
4303 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4305 description
=> 'Config type.',
4307 enum
=> ['user', 'network', 'meta'],
4317 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4319 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});