1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_modify_config_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (@$key_list) {
338 # some checks (e.g., disk, serial port, usb) need to be done somewhere
339 # else, as there the permission can be value dependend
340 next if PVE
::QemuServer
::is_valid_drivename
($opt);
341 next if $opt eq 'cdrom';
342 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
345 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
347 } elsif ($memoryoptions->{$opt}) {
348 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
349 } elsif ($hwtypeoptions->{$opt}) {
350 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
351 } elsif ($generaloptions->{$opt}) {
352 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
353 # special case for startup since it changes host behaviour
354 if ($opt eq 'startup') {
355 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
357 } elsif ($vmpoweroptions->{$opt}) {
358 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
359 } elsif ($diskoptions->{$opt}) {
360 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
361 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
362 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
363 } elsif ($cloudinitoptions->{$opt}) {
364 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
365 } elsif ($opt eq 'vmstate') {
366 # the user needs Disk and PowerMgmt privileges to change the vmstate
367 # also needs privileges on the storage, that will be checked later
368 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
370 # catches hostpci\d+, args, lock, etc.
371 # new options will be checked here
372 die "only root can set '$opt' config\n";
379 __PACKAGE__-
>register_method({
383 description
=> "Virtual machine index (per node).",
385 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
389 protected
=> 1, # qemu pid files are only readable by root
391 additionalProperties
=> 0,
393 node
=> get_standard_option
('pve-node'),
397 description
=> "Determine the full status of active VMs.",
405 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
407 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
412 my $rpcenv = PVE
::RPCEnvironment
::get
();
413 my $authuser = $rpcenv->get_user();
415 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
418 foreach my $vmid (keys %$vmstatus) {
419 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
421 my $data = $vmstatus->{$vmid};
428 my $parse_restore_archive = sub {
429 my ($storecfg, $archive) = @_;
431 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
433 if (defined($archive_storeid)) {
434 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
435 if ($scfg->{type
} eq 'pbs') {
442 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
450 __PACKAGE__-
>register_method({
454 description
=> "Create or restore a virtual machine.",
456 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
457 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
458 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
459 user
=> 'all', # check inside
464 additionalProperties
=> 0,
465 properties
=> PVE
::QemuServer
::json_config_properties
(
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
470 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
474 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
476 storage
=> get_standard_option
('pve-storage-id', {
477 description
=> "Default storage.",
479 completion
=> \
&PVE
::QemuServer
::complete_storage
,
484 description
=> "Allow to overwrite existing VM.",
485 requires
=> 'archive',
490 description
=> "Assign a unique random ethernet address.",
491 requires
=> 'archive',
495 type
=> 'string', format
=> 'pve-poolid',
496 description
=> "Add the VM to the specified pool.",
499 description
=> "Override I/O bandwidth limit (in KiB/s).",
503 default => 'restore limit from datacenter or storage config',
509 description
=> "Start VM after it was created successfully.",
519 my $rpcenv = PVE
::RPCEnvironment
::get
();
520 my $authuser = $rpcenv->get_user();
522 my $node = extract_param
($param, 'node');
523 my $vmid = extract_param
($param, 'vmid');
525 my $archive = extract_param
($param, 'archive');
526 my $is_restore = !!$archive;
528 my $bwlimit = extract_param
($param, 'bwlimit');
529 my $force = extract_param
($param, 'force');
530 my $pool = extract_param
($param, 'pool');
531 my $start_after_create = extract_param
($param, 'start');
532 my $storage = extract_param
($param, 'storage');
533 my $unique = extract_param
($param, 'unique');
535 if (defined(my $ssh_keys = $param->{sshkeys
})) {
536 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
537 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
540 PVE
::Cluster
::check_cfs_quorum
();
542 my $filename = PVE
::QemuConfig-
>config_file($vmid);
543 my $storecfg = PVE
::Storage
::config
();
545 if (defined($pool)) {
546 $rpcenv->check_pool_exist($pool);
549 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
550 if defined($storage);
552 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
554 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
556 } elsif ($archive && $force && (-f
$filename) &&
557 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
558 # OK: user has VM.Backup permissions, and want to restore an existing VM
564 &$resolve_cdrom_alias($param);
566 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
568 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
570 &$check_cpu_model_access($rpcenv, $authuser, $param);
572 foreach my $opt (keys %$param) {
573 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
574 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
575 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
577 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
578 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
582 PVE
::QemuServer
::add_random_macs
($param);
584 my $keystr = join(' ', keys %$param);
585 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
587 if ($archive eq '-') {
588 die "pipe requires cli environment\n"
589 if $rpcenv->{type
} ne 'cli';
590 $archive = { type
=> 'pipe' };
592 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
594 $archive = $parse_restore_archive->($storecfg, $archive);
598 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
600 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
601 die "$emsg $@" if $@;
603 my $restorefn = sub {
604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
606 PVE
::QemuConfig-
>check_protection($conf, $emsg);
608 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
611 my $restore_options = {
617 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
618 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
619 } elsif ($archive->{type
} eq 'pbs') {
620 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
622 die "unknown backup archive type\n";
624 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
625 # Convert restored VM to template if backup was VM template
626 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
627 warn "Convert to template.\n";
628 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
632 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
635 # ensure no old replication state are exists
636 PVE
::ReplicationState
::delete_guest_states
($vmid);
638 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
640 if ($start_after_create) {
641 print "Execute autostart\n";
642 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
648 # ensure no old replication state are exists
649 PVE
::ReplicationState
::delete_guest_states
($vmid);
653 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
657 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
659 if (!$conf->{boot
}) {
660 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
661 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
664 # auto generate uuid if user did not specify smbios1 option
665 if (!$conf->{smbios1
}) {
666 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
669 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
670 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
673 PVE
::QemuConfig-
>write_config($vmid, $conf);
679 foreach my $volid (@$vollist) {
680 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
686 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
689 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
691 if ($start_after_create) {
692 print "Execute autostart\n";
693 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
698 my ($code, $worker_name);
700 $worker_name = 'qmrestore';
702 eval { $restorefn->() };
704 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
710 $worker_name = 'qmcreate';
712 eval { $createfn->() };
715 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
716 unlink($conffile) or die "failed to remove config file: $!\n";
724 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
727 __PACKAGE__-
>register_method({
732 description
=> "Directory index",
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
748 subdir
=> { type
=> 'string' },
751 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
757 { subdir
=> 'config' },
758 { subdir
=> 'pending' },
759 { subdir
=> 'status' },
760 { subdir
=> 'unlink' },
761 { subdir
=> 'vncproxy' },
762 { subdir
=> 'termproxy' },
763 { subdir
=> 'migrate' },
764 { subdir
=> 'resize' },
765 { subdir
=> 'move' },
767 { subdir
=> 'rrddata' },
768 { subdir
=> 'monitor' },
769 { subdir
=> 'agent' },
770 { subdir
=> 'snapshot' },
771 { subdir
=> 'spiceproxy' },
772 { subdir
=> 'sendkey' },
773 { subdir
=> 'firewall' },
779 __PACKAGE__-
>register_method ({
780 subclass
=> "PVE::API2::Firewall::VM",
781 path
=> '{vmid}/firewall',
784 __PACKAGE__-
>register_method ({
785 subclass
=> "PVE::API2::Qemu::Agent",
786 path
=> '{vmid}/agent',
789 __PACKAGE__-
>register_method({
791 path
=> '{vmid}/rrd',
793 protected
=> 1, # fixme: can we avoid that?
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
797 description
=> "Read VM RRD statistics (returns PNG)",
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid'),
804 description
=> "Specify the time frame you are interested in.",
806 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
809 description
=> "The list of datasources you want to display.",
810 type
=> 'string', format
=> 'pve-configid-list',
813 description
=> "The RRD consolidation function",
815 enum
=> [ 'AVERAGE', 'MAX' ],
823 filename
=> { type
=> 'string' },
829 return PVE
::RRD
::create_rrd_graph
(
830 "pve2-vm/$param->{vmid}", $param->{timeframe
},
831 $param->{ds
}, $param->{cf
});
835 __PACKAGE__-
>register_method({
837 path
=> '{vmid}/rrddata',
839 protected
=> 1, # fixme: can we avoid that?
841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
843 description
=> "Read VM RRD statistics",
845 additionalProperties
=> 0,
847 node
=> get_standard_option
('pve-node'),
848 vmid
=> get_standard_option
('pve-vmid'),
850 description
=> "Specify the time frame you are interested in.",
852 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
855 description
=> "The RRD consolidation function",
857 enum
=> [ 'AVERAGE', 'MAX' ],
872 return PVE
::RRD
::create_rrd_data
(
873 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
877 __PACKAGE__-
>register_method({
879 path
=> '{vmid}/config',
882 description
=> "Get the virtual machine configuration with pending configuration " .
883 "changes applied. Set the 'current' parameter to get the current configuration instead.",
885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
888 additionalProperties
=> 0,
890 node
=> get_standard_option
('pve-node'),
891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
893 description
=> "Get current values (instead of pending values).",
898 snapshot
=> get_standard_option
('pve-snapshot-name', {
899 description
=> "Fetch config values from given snapshot.",
902 my ($cmd, $pname, $cur, $args) = @_;
903 PVE
::QemuConfig-
>snapshot_list($args->[0]);
909 description
=> "The VM configuration.",
911 properties
=> PVE
::QemuServer
::json_config_properties
({
914 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
921 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
922 current
=> "cannot use 'snapshot' parameter with 'current'"})
923 if ($param->{snapshot
} && $param->{current
});
926 if ($param->{snapshot
}) {
927 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
929 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
931 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
936 __PACKAGE__-
>register_method({
937 name
=> 'vm_pending',
938 path
=> '{vmid}/pending',
941 description
=> "Get the virtual machine configuration with both current and pending values.",
943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
946 additionalProperties
=> 0,
948 node
=> get_standard_option
('pve-node'),
949 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
958 description
=> "Configuration option name.",
962 description
=> "Current value.",
967 description
=> "Pending value.",
972 description
=> "Indicates a pending delete request if present and not 0. " .
973 "The value 2 indicates a force-delete request.",
985 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
987 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
989 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
990 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
992 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
995 # POST/PUT {vmid}/config implementation
997 # The original API used PUT (idempotent) an we assumed that all operations
998 # are fast. But it turned out that almost any configuration change can
999 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1000 # time to complete and have side effects (not idempotent).
1002 # The new implementation uses POST and forks a worker process. We added
1003 # a new option 'background_delay'. If specified we wait up to
1004 # 'background_delay' second for the worker task to complete. It returns null
1005 # if the task is finished within that time, else we return the UPID.
1007 my $update_vm_api = sub {
1008 my ($param, $sync) = @_;
1010 my $rpcenv = PVE
::RPCEnvironment
::get
();
1012 my $authuser = $rpcenv->get_user();
1014 my $node = extract_param
($param, 'node');
1016 my $vmid = extract_param
($param, 'vmid');
1018 my $digest = extract_param
($param, 'digest');
1020 my $background_delay = extract_param
($param, 'background_delay');
1022 if (defined(my $cipassword = $param->{cipassword
})) {
1023 # Same logic as in cloud-init (but with the regex fixed...)
1024 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1025 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1028 my @paramarr = (); # used for log message
1029 foreach my $key (sort keys %$param) {
1030 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1031 push @paramarr, "-$key", $value;
1034 my $skiplock = extract_param
($param, 'skiplock');
1035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1036 if $skiplock && $authuser ne 'root@pam';
1038 my $delete_str = extract_param
($param, 'delete');
1040 my $revert_str = extract_param
($param, 'revert');
1042 my $force = extract_param
($param, 'force');
1044 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1045 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1046 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1049 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1051 my $storecfg = PVE
::Storage
::config
();
1053 my $defaults = PVE
::QemuServer
::load_defaults
();
1055 &$resolve_cdrom_alias($param);
1057 # now try to verify all parameters
1060 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1061 if (!PVE
::QemuServer
::option_exists
($opt)) {
1062 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1065 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1066 "-revert $opt' at the same time" })
1067 if defined($param->{$opt});
1069 $revert->{$opt} = 1;
1073 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1074 $opt = 'ide2' if $opt eq 'cdrom';
1076 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1077 "-delete $opt' at the same time" })
1078 if defined($param->{$opt});
1080 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1081 "-revert $opt' at the same time" })
1084 if (!PVE
::QemuServer
::option_exists
($opt)) {
1085 raise_param_exc
({ delete => "unknown option '$opt'" });
1091 my $repl_conf = PVE
::ReplicationConfig-
>new();
1092 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1093 my $check_replication = sub {
1095 return if !$is_replicated;
1096 my $volid = $drive->{file
};
1097 return if !$volid || !($drive->{replicate
}//1);
1098 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1100 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1101 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1102 if !defined($storeid);
1104 return if defined($volname) && $volname eq 'cloudinit';
1107 if ($volid =~ $NEW_DISK_RE) {
1109 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1111 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1113 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1115 return if $scfg->{shared
};
1116 die "cannot add non-replicatable volume to a replicated VM\n";
1119 foreach my $opt (keys %$param) {
1120 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1121 # cleanup drive path
1122 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1123 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1124 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1125 $check_replication->($drive);
1126 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1127 } elsif ($opt =~ m/^net(\d+)$/) {
1129 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1130 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1131 } elsif ($opt eq 'vmgenid') {
1132 if ($param->{$opt} eq '1') {
1133 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1135 } elsif ($opt eq 'hookscript') {
1136 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1137 raise_param_exc
({ $opt => $@ }) if $@;
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1143 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1145 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1147 my $updatefn = sub {
1149 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1151 die "checksum missmatch (file change by other user?)\n"
1152 if $digest && $digest ne $conf->{digest
};
1154 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1156 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1157 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1158 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1159 delete $conf->{lock}; # for check lock check, not written out
1160 push @delete, 'lock'; # this is the real deal to write it out
1162 push @delete, 'runningmachine' if $conf->{runningmachine
};
1163 push @delete, 'runningcpu' if $conf->{runningcpu
};
1166 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1168 foreach my $opt (keys %$revert) {
1169 if (defined($conf->{$opt})) {
1170 $param->{$opt} = $conf->{$opt};
1171 } elsif (defined($conf->{pending
}->{$opt})) {
1176 if ($param->{memory
} || defined($param->{balloon
})) {
1177 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1178 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1180 die "balloon value too large (must be smaller than assigned memory)\n"
1181 if $balloon && $balloon > $maxmem;
1184 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1188 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1190 # write updates to pending section
1192 my $modified = {}; # record what $option we modify
1195 if (my $boot = $conf->{boot
}) {
1196 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1197 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1199 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1201 foreach my $opt (@delete) {
1202 $modified->{$opt} = 1;
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1205 # value of what we want to delete, independent if pending or not
1206 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1207 if (!defined($val)) {
1208 warn "cannot delete '$opt' - not set in current configuration!\n";
1209 $modified->{$opt} = 0;
1212 my $is_pending_val = defined($conf->{pending
}->{$opt});
1213 delete $conf->{pending
}->{$opt};
1215 # remove from bootorder if necessary
1216 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1217 @bootorder = grep {$_ ne $opt} @bootorder;
1218 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1219 $modified->{boot
} = 1;
1222 if ($opt =~ m/^unused/) {
1223 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1224 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1225 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1226 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1227 delete $conf->{$opt};
1228 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 } elsif ($opt eq 'vmstate') {
1231 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1232 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1233 delete $conf->{$opt};
1234 PVE
::QemuConfig-
>write_config($vmid, $conf);
1236 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1237 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1239 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1241 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1242 PVE
::QemuConfig-
>write_config($vmid, $conf);
1243 } elsif ($opt =~ m/^serial\d+$/) {
1244 if ($val eq 'socket') {
1245 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1246 } elsif ($authuser ne 'root@pam') {
1247 die "only root can delete '$opt' config for real devices\n";
1249 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1250 PVE
::QemuConfig-
>write_config($vmid, $conf);
1251 } elsif ($opt =~ m/^usb\d+$/) {
1252 if ($val =~ m/spice/) {
1253 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1254 } elsif ($authuser ne 'root@pam') {
1255 die "only root can delete '$opt' config for real devices\n";
1257 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1258 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1261 PVE
::QemuConfig-
>write_config($vmid, $conf);
1265 foreach my $opt (keys %$param) { # add/change
1266 $modified->{$opt} = 1;
1267 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1268 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1270 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1272 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1273 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1274 # FIXME: cloudinit: CDROM or Disk?
1275 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1276 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1278 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1280 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1281 if defined($conf->{pending
}->{$opt});
1283 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1284 } elsif ($opt =~ m/^serial\d+/) {
1285 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1286 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1287 } elsif ($authuser ne 'root@pam') {
1288 die "only root can modify '$opt' config for real devices\n";
1290 $conf->{pending
}->{$opt} = $param->{$opt};
1291 } elsif ($opt =~ m/^usb\d+/) {
1292 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1293 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1294 } elsif ($authuser ne 'root@pam') {
1295 die "only root can modify '$opt' config for real devices\n";
1297 $conf->{pending
}->{$opt} = $param->{$opt};
1299 $conf->{pending
}->{$opt} = $param->{$opt};
1301 if ($opt eq 'boot') {
1302 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1303 if ($new_bootcfg->{order
}) {
1304 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1305 for my $dev (@devs) {
1306 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1307 my $deleted = grep {$_ eq $dev} @delete;
1308 die "invalid bootorder: device '$dev' does not exist'\n"
1309 if !$exists || $deleted;
1312 # remove legacy boot order settings if new one set
1313 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1314 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1315 if $conf->{bootdisk
};
1319 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1320 PVE
::QemuConfig-
>write_config($vmid, $conf);
1323 # remove pending changes when nothing changed
1324 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1325 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1326 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1328 return if !scalar(keys %{$conf->{pending
}});
1330 my $running = PVE
::QemuServer
::check_running
($vmid);
1332 # apply pending changes
1334 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1338 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1340 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1342 raise_param_exc
($errors) if scalar(keys %$errors);
1351 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1353 if ($background_delay) {
1355 # Note: It would be better to do that in the Event based HTTPServer
1356 # to avoid blocking call to sleep.
1358 my $end_time = time() + $background_delay;
1360 my $task = PVE
::Tools
::upid_decode
($upid);
1363 while (time() < $end_time) {
1364 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1366 sleep(1); # this gets interrupted when child process ends
1370 my $status = PVE
::Tools
::upid_read_status
($upid);
1371 return if $status eq 'OK';
1380 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1383 my $vm_config_perm_list = [
1388 'VM.Config.Network',
1390 'VM.Config.Options',
1391 'VM.Config.Cloudinit',
1394 __PACKAGE__-
>register_method({
1395 name
=> 'update_vm_async',
1396 path
=> '{vmid}/config',
1400 description
=> "Set virtual machine options (asynchrounous API).",
1402 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1405 additionalProperties
=> 0,
1406 properties
=> PVE
::QemuServer
::json_config_properties
(
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1410 skiplock
=> get_standard_option
('skiplock'),
1412 type
=> 'string', format
=> 'pve-configid-list',
1413 description
=> "A list of settings you want to delete.",
1417 type
=> 'string', format
=> 'pve-configid-list',
1418 description
=> "Revert a pending change.",
1423 description
=> $opt_force_description,
1425 requires
=> 'delete',
1429 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1433 background_delay
=> {
1435 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1446 code
=> $update_vm_api,
1449 __PACKAGE__-
>register_method({
1450 name
=> 'update_vm',
1451 path
=> '{vmid}/config',
1455 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1457 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1460 additionalProperties
=> 0,
1461 properties
=> PVE
::QemuServer
::json_config_properties
(
1463 node
=> get_standard_option
('pve-node'),
1464 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1465 skiplock
=> get_standard_option
('skiplock'),
1467 type
=> 'string', format
=> 'pve-configid-list',
1468 description
=> "A list of settings you want to delete.",
1472 type
=> 'string', format
=> 'pve-configid-list',
1473 description
=> "Revert a pending change.",
1478 description
=> $opt_force_description,
1480 requires
=> 'delete',
1484 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1490 returns
=> { type
=> 'null' },
1493 &$update_vm_api($param, 1);
1498 __PACKAGE__-
>register_method({
1499 name
=> 'destroy_vm',
1504 description
=> "Destroy the VM and all used/owned volumes. Removes any VM specific permissions"
1505 ." and firewall rules",
1507 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1510 additionalProperties
=> 0,
1512 node
=> get_standard_option
('pve-node'),
1513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1514 skiplock
=> get_standard_option
('skiplock'),
1517 description
=> "Remove VMID from configurations, like backup & replication jobs and HA.",
1528 my $rpcenv = PVE
::RPCEnvironment
::get
();
1529 my $authuser = $rpcenv->get_user();
1530 my $vmid = $param->{vmid
};
1532 my $skiplock = $param->{skiplock
};
1533 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1534 if $skiplock && $authuser ne 'root@pam';
1536 my $early_checks = sub {
1538 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1539 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1541 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1543 if (!$param->{purge
}) {
1544 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1546 # don't allow destroy if with replication jobs but no purge param
1547 my $repl_conf = PVE
::ReplicationConfig-
>new();
1548 $repl_conf->check_for_existing_jobs($vmid);
1551 die "VM $vmid is running - destroy failed\n"
1552 if PVE
::QemuServer
::check_running
($vmid);
1562 my $storecfg = PVE
::Storage
::config
();
1564 syslog
('info', "destroy VM $vmid: $upid\n");
1565 PVE
::QemuConfig-
>lock_config($vmid, sub {
1566 # repeat, config might have changed
1567 my $ha_managed = $early_checks->();
1569 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1571 PVE
::AccessControl
::remove_vm_access
($vmid);
1572 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1573 if ($param->{purge
}) {
1574 print "purging VM $vmid from related configurations..\n";
1575 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1576 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1579 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1580 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1584 # only now remove the zombie config, else we can have reuse race
1585 PVE
::QemuConfig-
>destroy_config($vmid);
1589 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1592 __PACKAGE__-
>register_method({
1594 path
=> '{vmid}/unlink',
1598 description
=> "Unlink/delete disk images.",
1600 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1603 additionalProperties
=> 0,
1605 node
=> get_standard_option
('pve-node'),
1606 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1608 type
=> 'string', format
=> 'pve-configid-list',
1609 description
=> "A list of disk IDs you want to delete.",
1613 description
=> $opt_force_description,
1618 returns
=> { type
=> 'null'},
1622 $param->{delete} = extract_param
($param, 'idlist');
1624 __PACKAGE__-
>update_vm($param);
1629 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1630 my $gen_rand_chars = sub {
1633 die "invalid length $length" if $length < 1;
1635 my $min = ord('!'); # first printable ascii
1637 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1638 die "failed to generate random bytes!\n"
1641 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1648 __PACKAGE__-
>register_method({
1650 path
=> '{vmid}/vncproxy',
1654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1656 description
=> "Creates a TCP VNC proxy connections.",
1658 additionalProperties
=> 0,
1660 node
=> get_standard_option
('pve-node'),
1661 vmid
=> get_standard_option
('pve-vmid'),
1665 description
=> "starts websockify instead of vncproxy",
1667 'generate-password' => {
1671 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1676 additionalProperties
=> 0,
1678 user
=> { type
=> 'string' },
1679 ticket
=> { type
=> 'string' },
1682 description
=> "Returned if requested with 'generate-password' param."
1683 ." Consists of printable ASCII characters ('!' .. '~').",
1686 cert
=> { type
=> 'string' },
1687 port
=> { type
=> 'integer' },
1688 upid
=> { type
=> 'string' },
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $vmid = $param->{vmid
};
1699 my $node = $param->{node
};
1700 my $websocket = $param->{websocket
};
1702 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1706 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1707 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1710 my $authpath = "/vms/$vmid";
1712 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1713 my $password = $ticket;
1714 if ($param->{'generate-password'}) {
1715 $password = $gen_rand_chars->(8);
1718 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1724 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1725 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1726 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1727 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1728 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1730 $family = PVE
::Tools
::get_host_address_family
($node);
1733 my $port = PVE
::Tools
::next_vnc_port
($family);
1740 syslog
('info', "starting vnc proxy $upid\n");
1744 if (defined($serial)) {
1746 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1748 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1749 '-timeout', $timeout, '-authpath', $authpath,
1750 '-perm', 'Sys.Console'];
1752 if ($param->{websocket
}) {
1753 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1754 push @$cmd, '-notls', '-listen', 'localhost';
1757 push @$cmd, '-c', @$remcmd, @$termcmd;
1759 PVE
::Tools
::run_command
($cmd);
1763 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1765 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1767 my $sock = IO
::Socket
::IP-
>new(
1772 GetAddrInfoFlags
=> 0,
1773 ) or die "failed to create socket: $!\n";
1774 # Inside the worker we shouldn't have any previous alarms
1775 # running anyway...:
1777 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1779 accept(my $cli, $sock) or die "connection failed: $!\n";
1782 if (PVE
::Tools
::run_command
($cmd,
1783 output
=> '>&'.fileno($cli),
1784 input
=> '<&'.fileno($cli),
1787 die "Failed to run vncproxy.\n";
1794 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1796 PVE
::Tools
::wait_for_vnc_port
($port);
1805 $res->{password
} = $password if $param->{'generate-password'};
1810 __PACKAGE__-
>register_method({
1811 name
=> 'termproxy',
1812 path
=> '{vmid}/termproxy',
1816 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1818 description
=> "Creates a TCP proxy connections.",
1820 additionalProperties
=> 0,
1822 node
=> get_standard_option
('pve-node'),
1823 vmid
=> get_standard_option
('pve-vmid'),
1827 enum
=> [qw(serial0 serial1 serial2 serial3)],
1828 description
=> "opens a serial terminal (defaults to display)",
1833 additionalProperties
=> 0,
1835 user
=> { type
=> 'string' },
1836 ticket
=> { type
=> 'string' },
1837 port
=> { type
=> 'integer' },
1838 upid
=> { type
=> 'string' },
1844 my $rpcenv = PVE
::RPCEnvironment
::get
();
1846 my $authuser = $rpcenv->get_user();
1848 my $vmid = $param->{vmid
};
1849 my $node = $param->{node
};
1850 my $serial = $param->{serial
};
1852 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1854 if (!defined($serial)) {
1856 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1857 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1861 my $authpath = "/vms/$vmid";
1863 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1868 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1869 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1870 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1871 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1872 push @$remcmd, '--';
1874 $family = PVE
::Tools
::get_host_address_family
($node);
1877 my $port = PVE
::Tools
::next_vnc_port
($family);
1879 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1880 push @$termcmd, '-iface', $serial if $serial;
1885 syslog
('info', "starting qemu termproxy $upid\n");
1887 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1888 '--perm', 'VM.Console', '--'];
1889 push @$cmd, @$remcmd, @$termcmd;
1891 PVE
::Tools
::run_command
($cmd);
1894 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1896 PVE
::Tools
::wait_for_vnc_port
($port);
1906 __PACKAGE__-
>register_method({
1907 name
=> 'vncwebsocket',
1908 path
=> '{vmid}/vncwebsocket',
1911 description
=> "You also need to pass a valid ticket (vncticket).",
1912 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1914 description
=> "Opens a weksocket for VNC traffic.",
1916 additionalProperties
=> 0,
1918 node
=> get_standard_option
('pve-node'),
1919 vmid
=> get_standard_option
('pve-vmid'),
1921 description
=> "Ticket from previous call to vncproxy.",
1926 description
=> "Port number returned by previous vncproxy call.",
1936 port
=> { type
=> 'string' },
1942 my $rpcenv = PVE
::RPCEnvironment
::get
();
1944 my $authuser = $rpcenv->get_user();
1946 my $vmid = $param->{vmid
};
1947 my $node = $param->{node
};
1949 my $authpath = "/vms/$vmid";
1951 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1953 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1955 # Note: VNC ports are acessible from outside, so we do not gain any
1956 # security if we verify that $param->{port} belongs to VM $vmid. This
1957 # check is done by verifying the VNC ticket (inside VNC protocol).
1959 my $port = $param->{port
};
1961 return { port
=> $port };
1964 __PACKAGE__-
>register_method({
1965 name
=> 'spiceproxy',
1966 path
=> '{vmid}/spiceproxy',
1971 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1973 description
=> "Returns a SPICE configuration to connect to the VM.",
1975 additionalProperties
=> 0,
1977 node
=> get_standard_option
('pve-node'),
1978 vmid
=> get_standard_option
('pve-vmid'),
1979 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1982 returns
=> get_standard_option
('remote-viewer-config'),
1986 my $rpcenv = PVE
::RPCEnvironment
::get
();
1988 my $authuser = $rpcenv->get_user();
1990 my $vmid = $param->{vmid
};
1991 my $node = $param->{node
};
1992 my $proxy = $param->{proxy
};
1994 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1995 my $title = "VM $vmid";
1996 $title .= " - ". $conf->{name
} if $conf->{name
};
1998 my $port = PVE
::QemuServer
::spice_port
($vmid);
2000 my ($ticket, undef, $remote_viewer_config) =
2001 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2003 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2004 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2006 return $remote_viewer_config;
2009 __PACKAGE__-
>register_method({
2011 path
=> '{vmid}/status',
2014 description
=> "Directory index",
2019 additionalProperties
=> 0,
2021 node
=> get_standard_option
('pve-node'),
2022 vmid
=> get_standard_option
('pve-vmid'),
2030 subdir
=> { type
=> 'string' },
2033 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2039 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2042 { subdir
=> 'current' },
2043 { subdir
=> 'start' },
2044 { subdir
=> 'stop' },
2045 { subdir
=> 'reset' },
2046 { subdir
=> 'shutdown' },
2047 { subdir
=> 'suspend' },
2048 { subdir
=> 'reboot' },
2054 __PACKAGE__-
>register_method({
2055 name
=> 'vm_status',
2056 path
=> '{vmid}/status/current',
2059 protected
=> 1, # qemu pid files are only readable by root
2060 description
=> "Get virtual machine status.",
2062 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2065 additionalProperties
=> 0,
2067 node
=> get_standard_option
('pve-node'),
2068 vmid
=> get_standard_option
('pve-vmid'),
2074 %$PVE::QemuServer
::vmstatus_return_properties
,
2076 description
=> "HA manager service status.",
2080 description
=> "Qemu VGA configuration supports spice.",
2085 description
=> "Qemu GuestAgent enabled in config.",
2095 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2097 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2098 my $status = $vmstatus->{$param->{vmid
}};
2100 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2102 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2103 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2108 __PACKAGE__-
>register_method({
2110 path
=> '{vmid}/status/start',
2114 description
=> "Start virtual machine.",
2116 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2119 additionalProperties
=> 0,
2121 node
=> get_standard_option
('pve-node'),
2122 vmid
=> get_standard_option
('pve-vmid',
2123 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2124 skiplock
=> get_standard_option
('skiplock'),
2125 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2126 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2129 enum
=> ['secure', 'insecure'],
2130 description
=> "Migration traffic is encrypted using an SSH " .
2131 "tunnel by default. On secure, completely private networks " .
2132 "this can be disabled to increase performance.",
2135 migration_network
=> {
2136 type
=> 'string', format
=> 'CIDR',
2137 description
=> "CIDR of the (sub) network that is used for migration.",
2140 machine
=> get_standard_option
('pve-qemu-machine'),
2142 description
=> "Override QEMU's -cpu argument with the given string.",
2146 targetstorage
=> get_standard_option
('pve-targetstorage'),
2148 description
=> "Wait maximal timeout seconds.",
2151 default => 'max(30, vm memory in GiB)',
2162 my $rpcenv = PVE
::RPCEnvironment
::get
();
2163 my $authuser = $rpcenv->get_user();
2165 my $node = extract_param
($param, 'node');
2166 my $vmid = extract_param
($param, 'vmid');
2167 my $timeout = extract_param
($param, 'timeout');
2169 my $machine = extract_param
($param, 'machine');
2170 my $force_cpu = extract_param
($param, 'force-cpu');
2172 my $get_root_param = sub {
2173 my $value = extract_param
($param, $_[0]);
2174 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2175 if $value && $authuser ne 'root@pam';
2179 my $stateuri = $get_root_param->('stateuri');
2180 my $skiplock = $get_root_param->('skiplock');
2181 my $migratedfrom = $get_root_param->('migratedfrom');
2182 my $migration_type = $get_root_param->('migration_type');
2183 my $migration_network = $get_root_param->('migration_network');
2184 my $targetstorage = $get_root_param->('targetstorage');
2188 if ($targetstorage) {
2189 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2191 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2192 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2196 # read spice ticket from STDIN
2198 my $nbd_protocol_version = 0;
2199 my $replicated_volumes = {};
2200 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2201 while (defined(my $line = <STDIN
>)) {
2203 if ($line =~ m/^spice_ticket: (.+)$/) {
2205 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2206 $nbd_protocol_version = $1;
2207 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2208 $replicated_volumes->{$1} = 1;
2210 # fallback for old source node
2211 $spice_ticket = $line;
2216 PVE
::Cluster
::check_cfs_quorum
();
2218 my $storecfg = PVE
::Storage
::config
();
2220 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2224 print "Requesting HA start for VM $vmid\n";
2226 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2227 PVE
::Tools
::run_command
($cmd);
2231 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2238 syslog
('info', "start VM $vmid: $upid\n");
2240 my $migrate_opts = {
2241 migratedfrom
=> $migratedfrom,
2242 spice_ticket
=> $spice_ticket,
2243 network
=> $migration_network,
2244 type
=> $migration_type,
2245 storagemap
=> $storagemap,
2246 nbd_proto_version
=> $nbd_protocol_version,
2247 replicated_volumes
=> $replicated_volumes,
2251 statefile
=> $stateuri,
2252 skiplock
=> $skiplock,
2253 forcemachine
=> $machine,
2254 timeout
=> $timeout,
2255 forcecpu
=> $force_cpu,
2258 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2262 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2266 __PACKAGE__-
>register_method({
2268 path
=> '{vmid}/status/stop',
2272 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2273 "is akin to pulling the power plug of a running computer and may damage the VM data",
2275 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2278 additionalProperties
=> 0,
2280 node
=> get_standard_option
('pve-node'),
2281 vmid
=> get_standard_option
('pve-vmid',
2282 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2283 skiplock
=> get_standard_option
('skiplock'),
2284 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2286 description
=> "Wait maximal timeout seconds.",
2292 description
=> "Do not deactivate storage volumes.",
2305 my $rpcenv = PVE
::RPCEnvironment
::get
();
2306 my $authuser = $rpcenv->get_user();
2308 my $node = extract_param
($param, 'node');
2309 my $vmid = extract_param
($param, 'vmid');
2311 my $skiplock = extract_param
($param, 'skiplock');
2312 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2313 if $skiplock && $authuser ne 'root@pam';
2315 my $keepActive = extract_param
($param, 'keepActive');
2316 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2317 if $keepActive && $authuser ne 'root@pam';
2319 my $migratedfrom = extract_param
($param, 'migratedfrom');
2320 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2321 if $migratedfrom && $authuser ne 'root@pam';
2324 my $storecfg = PVE
::Storage
::config
();
2326 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2331 print "Requesting HA stop for VM $vmid\n";
2333 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2334 PVE
::Tools
::run_command
($cmd);
2338 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2344 syslog
('info', "stop VM $vmid: $upid\n");
2346 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2347 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2351 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2355 __PACKAGE__-
>register_method({
2357 path
=> '{vmid}/status/reset',
2361 description
=> "Reset virtual machine.",
2363 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2366 additionalProperties
=> 0,
2368 node
=> get_standard_option
('pve-node'),
2369 vmid
=> get_standard_option
('pve-vmid',
2370 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2371 skiplock
=> get_standard_option
('skiplock'),
2380 my $rpcenv = PVE
::RPCEnvironment
::get
();
2382 my $authuser = $rpcenv->get_user();
2384 my $node = extract_param
($param, 'node');
2386 my $vmid = extract_param
($param, 'vmid');
2388 my $skiplock = extract_param
($param, 'skiplock');
2389 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2390 if $skiplock && $authuser ne 'root@pam';
2392 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2397 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2402 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2405 my sub vm_is_paused
{
2407 my $qmpstatus = eval {
2408 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2409 mon_cmd
($vmid, "query-status");
2412 return $qmpstatus && $qmpstatus->{status
} eq "paused";
2415 __PACKAGE__-
>register_method({
2416 name
=> 'vm_shutdown',
2417 path
=> '{vmid}/status/shutdown',
2421 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2422 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2424 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2427 additionalProperties
=> 0,
2429 node
=> get_standard_option
('pve-node'),
2430 vmid
=> get_standard_option
('pve-vmid',
2431 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2432 skiplock
=> get_standard_option
('skiplock'),
2434 description
=> "Wait maximal timeout seconds.",
2440 description
=> "Make sure the VM stops.",
2446 description
=> "Do not deactivate storage volumes.",
2459 my $rpcenv = PVE
::RPCEnvironment
::get
();
2460 my $authuser = $rpcenv->get_user();
2462 my $node = extract_param
($param, 'node');
2463 my $vmid = extract_param
($param, 'vmid');
2465 my $skiplock = extract_param
($param, 'skiplock');
2466 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2467 if $skiplock && $authuser ne 'root@pam';
2469 my $keepActive = extract_param
($param, 'keepActive');
2470 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2471 if $keepActive && $authuser ne 'root@pam';
2473 my $storecfg = PVE
::Storage
::config
();
2477 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2478 # otherwise, we will infer a shutdown command, but run into the timeout,
2479 # then when the vm is resumed, it will instantly shutdown
2481 # checking the qmp status here to get feedback to the gui/cli/api
2482 # and the status query should not take too long
2483 if (vm_is_paused
($vmid)) {
2484 if ($param->{forceStop
}) {
2485 warn "VM is paused - stop instead of shutdown\n";
2488 die "VM is paused - cannot shutdown\n";
2492 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2494 my $timeout = $param->{timeout
} // 60;
2498 print "Requesting HA stop for VM $vmid\n";
2500 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2501 PVE
::Tools
::run_command
($cmd);
2505 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2512 syslog
('info', "shutdown VM $vmid: $upid\n");
2514 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2515 $shutdown, $param->{forceStop
}, $keepActive);
2519 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2523 __PACKAGE__-
>register_method({
2524 name
=> 'vm_reboot',
2525 path
=> '{vmid}/status/reboot',
2529 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2531 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2534 additionalProperties
=> 0,
2536 node
=> get_standard_option
('pve-node'),
2537 vmid
=> get_standard_option
('pve-vmid',
2538 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2540 description
=> "Wait maximal timeout seconds for the shutdown.",
2553 my $rpcenv = PVE
::RPCEnvironment
::get
();
2554 my $authuser = $rpcenv->get_user();
2556 my $node = extract_param
($param, 'node');
2557 my $vmid = extract_param
($param, 'vmid');
2559 die "VM is paused - cannot shutdown\n" if vm_is_paused
($vmid);
2561 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2566 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2567 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2571 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2574 __PACKAGE__-
>register_method({
2575 name
=> 'vm_suspend',
2576 path
=> '{vmid}/status/suspend',
2580 description
=> "Suspend virtual machine.",
2582 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2583 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2584 " on the storage for the vmstate.",
2585 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2588 additionalProperties
=> 0,
2590 node
=> get_standard_option
('pve-node'),
2591 vmid
=> get_standard_option
('pve-vmid',
2592 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2593 skiplock
=> get_standard_option
('skiplock'),
2598 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2600 statestorage
=> get_standard_option
('pve-storage-id', {
2601 description
=> "The storage for the VM state",
2602 requires
=> 'todisk',
2604 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2614 my $rpcenv = PVE
::RPCEnvironment
::get
();
2615 my $authuser = $rpcenv->get_user();
2617 my $node = extract_param
($param, 'node');
2618 my $vmid = extract_param
($param, 'vmid');
2620 my $todisk = extract_param
($param, 'todisk') // 0;
2622 my $statestorage = extract_param
($param, 'statestorage');
2624 my $skiplock = extract_param
($param, 'skiplock');
2625 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2626 if $skiplock && $authuser ne 'root@pam';
2628 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2630 die "Cannot suspend HA managed VM to disk\n"
2631 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2633 # early check for storage permission, for better user feedback
2635 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2637 if (!$statestorage) {
2638 # get statestorage from config if none is given
2639 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2640 my $storecfg = PVE
::Storage
::config
();
2641 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2644 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2650 syslog
('info', "suspend VM $vmid: $upid\n");
2652 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2657 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2658 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2661 __PACKAGE__-
>register_method({
2662 name
=> 'vm_resume',
2663 path
=> '{vmid}/status/resume',
2667 description
=> "Resume virtual machine.",
2669 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2672 additionalProperties
=> 0,
2674 node
=> get_standard_option
('pve-node'),
2675 vmid
=> get_standard_option
('pve-vmid',
2676 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2677 skiplock
=> get_standard_option
('skiplock'),
2678 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2688 my $rpcenv = PVE
::RPCEnvironment
::get
();
2690 my $authuser = $rpcenv->get_user();
2692 my $node = extract_param
($param, 'node');
2694 my $vmid = extract_param
($param, 'vmid');
2696 my $skiplock = extract_param
($param, 'skiplock');
2697 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2698 if $skiplock && $authuser ne 'root@pam';
2700 my $nocheck = extract_param
($param, 'nocheck');
2701 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2702 if $nocheck && $authuser ne 'root@pam';
2704 my $to_disk_suspended;
2706 PVE
::QemuConfig-
>lock_config($vmid, sub {
2707 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2708 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2712 die "VM $vmid not running\n"
2713 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2718 syslog
('info', "resume VM $vmid: $upid\n");
2720 if (!$to_disk_suspended) {
2721 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2723 my $storecfg = PVE
::Storage
::config
();
2724 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2730 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2733 __PACKAGE__-
>register_method({
2734 name
=> 'vm_sendkey',
2735 path
=> '{vmid}/sendkey',
2739 description
=> "Send key event to virtual machine.",
2741 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2744 additionalProperties
=> 0,
2746 node
=> get_standard_option
('pve-node'),
2747 vmid
=> get_standard_option
('pve-vmid',
2748 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2749 skiplock
=> get_standard_option
('skiplock'),
2751 description
=> "The key (qemu monitor encoding).",
2756 returns
=> { type
=> 'null'},
2760 my $rpcenv = PVE
::RPCEnvironment
::get
();
2762 my $authuser = $rpcenv->get_user();
2764 my $node = extract_param
($param, 'node');
2766 my $vmid = extract_param
($param, 'vmid');
2768 my $skiplock = extract_param
($param, 'skiplock');
2769 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2770 if $skiplock && $authuser ne 'root@pam';
2772 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2777 __PACKAGE__-
>register_method({
2778 name
=> 'vm_feature',
2779 path
=> '{vmid}/feature',
2783 description
=> "Check if feature for virtual machine is available.",
2785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2788 additionalProperties
=> 0,
2790 node
=> get_standard_option
('pve-node'),
2791 vmid
=> get_standard_option
('pve-vmid'),
2793 description
=> "Feature to check.",
2795 enum
=> [ 'snapshot', 'clone', 'copy' ],
2797 snapname
=> get_standard_option
('pve-snapshot-name', {
2805 hasFeature
=> { type
=> 'boolean' },
2808 items
=> { type
=> 'string' },
2815 my $node = extract_param
($param, 'node');
2817 my $vmid = extract_param
($param, 'vmid');
2819 my $snapname = extract_param
($param, 'snapname');
2821 my $feature = extract_param
($param, 'feature');
2823 my $running = PVE
::QemuServer
::check_running
($vmid);
2825 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2828 my $snap = $conf->{snapshots
}->{$snapname};
2829 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2832 my $storecfg = PVE
::Storage
::config
();
2834 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2835 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2838 hasFeature
=> $hasFeature,
2839 nodes
=> [ keys %$nodelist ],
2843 __PACKAGE__-
>register_method({
2845 path
=> '{vmid}/clone',
2849 description
=> "Create a copy of virtual machine/template.",
2851 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2852 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2853 "'Datastore.AllocateSpace' on any used storage.",
2856 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2858 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2859 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2864 additionalProperties
=> 0,
2866 node
=> get_standard_option
('pve-node'),
2867 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2868 newid
=> get_standard_option
('pve-vmid', {
2869 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2870 description
=> 'VMID for the clone.' }),
2873 type
=> 'string', format
=> 'dns-name',
2874 description
=> "Set a name for the new VM.",
2879 description
=> "Description for the new VM.",
2883 type
=> 'string', format
=> 'pve-poolid',
2884 description
=> "Add the new VM to the specified pool.",
2886 snapname
=> get_standard_option
('pve-snapshot-name', {
2889 storage
=> get_standard_option
('pve-storage-id', {
2890 description
=> "Target storage for full clone.",
2894 description
=> "Target format for file storage. Only valid for full clone.",
2897 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2902 description
=> "Create a full copy of all disks. This is always done when " .
2903 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2905 target
=> get_standard_option
('pve-node', {
2906 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2910 description
=> "Override I/O bandwidth limit (in KiB/s).",
2914 default => 'clone limit from datacenter or storage config',
2924 my $rpcenv = PVE
::RPCEnvironment
::get
();
2925 my $authuser = $rpcenv->get_user();
2927 my $node = extract_param
($param, 'node');
2928 my $vmid = extract_param
($param, 'vmid');
2929 my $newid = extract_param
($param, 'newid');
2930 my $pool = extract_param
($param, 'pool');
2931 $rpcenv->check_pool_exist($pool) if defined($pool);
2933 my $snapname = extract_param
($param, 'snapname');
2934 my $storage = extract_param
($param, 'storage');
2935 my $format = extract_param
($param, 'format');
2936 my $target = extract_param
($param, 'target');
2938 my $localnode = PVE
::INotify
::nodename
();
2940 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2944 PVE
::Cluster
::check_node_exists
($target) if $target;
2946 my $storecfg = PVE
::Storage
::config
();
2949 # check if storage is enabled on local node
2950 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2952 # check if storage is available on target node
2953 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2954 # clone only works if target storage is shared
2955 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2956 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2960 PVE
::Cluster
::check_cfs_quorum
();
2962 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2965 # do all tests after lock but before forking worker - if possible
2967 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2968 PVE
::QemuConfig-
>check_lock($conf);
2970 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2971 die "unexpected state change\n" if $verify_running != $running;
2973 die "snapshot '$snapname' does not exist\n"
2974 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2976 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2978 die "parameter 'storage' not allowed for linked clones\n"
2979 if defined($storage) && !$full;
2981 die "parameter 'format' not allowed for linked clones\n"
2982 if defined($format) && !$full;
2984 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2986 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2988 die "can't clone VM to node '$target' (VM uses local storage)\n"
2989 if $target && !$sharedvm;
2991 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2992 die "unable to create VM $newid: config file already exists\n"
2995 my $newconf = { lock => 'clone' };
3000 foreach my $opt (keys %$oldconf) {
3001 my $value = $oldconf->{$opt};
3003 # do not copy snapshot related info
3004 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3005 $opt eq 'vmstate' || $opt eq 'snapstate';
3007 # no need to copy unused images, because VMID(owner) changes anyways
3008 next if $opt =~ m/^unused\d+$/;
3010 # always change MAC! address
3011 if ($opt =~ m/^net(\d+)$/) {
3012 my $net = PVE
::QemuServer
::parse_net
($value);
3013 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3014 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3015 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3016 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3017 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3018 die "unable to parse drive options for '$opt'\n" if !$drive;
3019 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3020 $newconf->{$opt} = $value; # simply copy configuration
3022 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3023 die "Full clone feature is not supported for drive '$opt'\n"
3024 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3025 $fullclone->{$opt} = 1;
3027 # not full means clone instead of copy
3028 die "Linked clone feature is not supported for drive '$opt'\n"
3029 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3031 $drives->{$opt} = $drive;
3032 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3033 push @$vollist, $drive->{file
};
3036 # copy everything else
3037 $newconf->{$opt} = $value;
3041 # auto generate a new uuid
3042 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3043 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3044 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3045 # auto generate a new vmgenid only if the option was set for template
3046 if ($newconf->{vmgenid
}) {
3047 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3050 delete $newconf->{template
};
3052 if ($param->{name
}) {
3053 $newconf->{name
} = $param->{name
};
3055 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3058 if ($param->{description
}) {
3059 $newconf->{description
} = $param->{description
};
3062 # create empty/temp config - this fails if VM already exists on other node
3063 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3064 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3069 my $newvollist = [];
3076 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3078 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3080 my $bwlimit = extract_param
($param, 'bwlimit');
3082 my $total_jobs = scalar(keys %{$drives});
3085 foreach my $opt (keys %$drives) {
3086 my $drive = $drives->{$opt};
3087 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3088 my $completion = $skipcomplete ?
'skip' : 'complete';
3090 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3091 my $storage_list = [ $src_sid ];
3092 push @$storage_list, $storage if defined($storage);
3093 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3095 my $newdrive = PVE
::QemuServer
::clone_disk
(
3114 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3116 PVE
::QemuConfig-
>write_config($newid, $newconf);
3120 delete $newconf->{lock};
3122 # do not write pending changes
3123 if (my @changes = keys %{$newconf->{pending
}}) {
3124 my $pending = join(',', @changes);
3125 warn "found pending changes for '$pending', discarding for clone\n";
3126 delete $newconf->{pending
};
3129 PVE
::QemuConfig-
>write_config($newid, $newconf);
3132 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3133 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3134 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3136 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3137 die "Failed to move config to node '$target' - rename failed: $!\n"
3138 if !rename($conffile, $newconffile);
3141 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3144 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3145 sleep 1; # some storage like rbd need to wait before release volume - really?
3147 foreach my $volid (@$newvollist) {
3148 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3152 PVE
::Firewall
::remove_vmfw_conf
($newid);
3154 unlink $conffile; # avoid races -> last thing before die
3156 die "clone failed: $err";
3162 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3164 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3167 # Aquire exclusive lock lock for $newid
3168 my $lock_target_vm = sub {
3169 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3172 # exclusive lock if VM is running - else shared lock is enough;
3174 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3176 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3180 __PACKAGE__-
>register_method({
3181 name
=> 'move_vm_disk',
3182 path
=> '{vmid}/move_disk',
3186 description
=> "Move volume to different storage.",
3188 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3190 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3191 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3195 additionalProperties
=> 0,
3197 node
=> get_standard_option
('pve-node'),
3198 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3201 description
=> "The disk you want to move.",
3202 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3204 storage
=> get_standard_option
('pve-storage-id', {
3205 description
=> "Target storage.",
3206 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3210 description
=> "Target Format.",
3211 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3216 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3222 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3227 description
=> "Override I/O bandwidth limit (in KiB/s).",
3231 default => 'move limit from datacenter or storage config',
3237 description
=> "the task ID.",
3242 my $rpcenv = PVE
::RPCEnvironment
::get
();
3243 my $authuser = $rpcenv->get_user();
3245 my $node = extract_param
($param, 'node');
3246 my $vmid = extract_param
($param, 'vmid');
3247 my $digest = extract_param
($param, 'digest');
3248 my $disk = extract_param
($param, 'disk');
3249 my $storeid = extract_param
($param, 'storage');
3250 my $format = extract_param
($param, 'format');
3252 my $storecfg = PVE
::Storage
::config
();
3254 my $updatefn = sub {
3255 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3256 PVE
::QemuConfig-
>check_lock($conf);
3258 die "VM config checksum missmatch (file change by other user?)\n"
3259 if $digest && $digest ne $conf->{digest
};
3261 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3263 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3265 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3266 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3268 my $old_volid = $drive->{file
};
3270 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3271 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3275 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3276 (!$format || !$oldfmt || $oldfmt eq $format);
3278 # this only checks snapshots because $disk is passed!
3279 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3280 die "you can't move a disk with snapshots and delete the source\n"
3281 if $snapshotted && $param->{delete};
3283 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3285 my $running = PVE
::QemuServer
::check_running
($vmid);
3287 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3290 my $newvollist = [];
3296 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3298 warn "moving disk with snapshots, snapshots will not be moved!\n"
3301 my $bwlimit = extract_param
($param, 'bwlimit');
3302 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3304 my $newdrive = PVE
::QemuServer
::clone_disk
(
3322 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3324 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3326 # convert moved disk to base if part of template
3327 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3328 if PVE
::QemuConfig-
>is_template($conf);
3330 PVE
::QemuConfig-
>write_config($vmid, $conf);
3332 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3333 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3334 eval { mon_cmd
($vmid, "guest-fstrim") };
3338 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3339 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3345 foreach my $volid (@$newvollist) {
3346 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3349 die "storage migration failed: $err";
3352 if ($param->{delete}) {
3354 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3355 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3361 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3364 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3367 my $check_vm_disks_local = sub {
3368 my ($storecfg, $vmconf, $vmid) = @_;
3370 my $local_disks = {};
3372 # add some more information to the disks e.g. cdrom
3373 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3374 my ($volid, $attr) = @_;
3376 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3378 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3379 return if $scfg->{shared
};
3381 # The shared attr here is just a special case where the vdisk
3382 # is marked as shared manually
3383 return if $attr->{shared
};
3384 return if $attr->{cdrom
} and $volid eq "none";
3386 if (exists $local_disks->{$volid}) {
3387 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3389 $local_disks->{$volid} = $attr;
3390 # ensure volid is present in case it's needed
3391 $local_disks->{$volid}->{volid
} = $volid;
3395 return $local_disks;
3398 __PACKAGE__-
>register_method({
3399 name
=> 'migrate_vm_precondition',
3400 path
=> '{vmid}/migrate',
3404 description
=> "Get preconditions for migration.",
3406 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3409 additionalProperties
=> 0,
3411 node
=> get_standard_option
('pve-node'),
3412 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3413 target
=> get_standard_option
('pve-node', {
3414 description
=> "Target node.",
3415 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3423 running
=> { type
=> 'boolean' },
3427 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3429 not_allowed_nodes
=> {
3432 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3436 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3438 local_resources
=> {
3440 description
=> "List local resources e.g. pci, usb"
3447 my $rpcenv = PVE
::RPCEnvironment
::get
();
3449 my $authuser = $rpcenv->get_user();
3451 PVE
::Cluster
::check_cfs_quorum
();
3455 my $vmid = extract_param
($param, 'vmid');
3456 my $target = extract_param
($param, 'target');
3457 my $localnode = PVE
::INotify
::nodename
();
3461 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3462 my $storecfg = PVE
::Storage
::config
();
3465 # try to detect errors early
3466 PVE
::QemuConfig-
>check_lock($vmconf);
3468 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3470 # if vm is not running, return target nodes where local storage is available
3471 # for offline migration
3472 if (!$res->{running
}) {
3473 $res->{allowed_nodes
} = [];
3474 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3475 delete $checked_nodes->{$localnode};
3477 foreach my $node (keys %$checked_nodes) {
3478 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3479 push @{$res->{allowed_nodes
}}, $node;
3483 $res->{not_allowed_nodes
} = $checked_nodes;
3487 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3488 $res->{local_disks
} = [ values %$local_disks ];;
3490 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3492 $res->{local_resources
} = $local_resources;
3499 __PACKAGE__-
>register_method({
3500 name
=> 'migrate_vm',
3501 path
=> '{vmid}/migrate',
3505 description
=> "Migrate virtual machine. Creates a new migration task.",
3507 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3510 additionalProperties
=> 0,
3512 node
=> get_standard_option
('pve-node'),
3513 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3514 target
=> get_standard_option
('pve-node', {
3515 description
=> "Target node.",
3516 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3520 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3525 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3530 enum
=> ['secure', 'insecure'],
3531 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3534 migration_network
=> {
3535 type
=> 'string', format
=> 'CIDR',
3536 description
=> "CIDR of the (sub) network that is used for migration.",
3539 "with-local-disks" => {
3541 description
=> "Enable live storage migration for local disk",
3544 targetstorage
=> get_standard_option
('pve-targetstorage', {
3545 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3548 description
=> "Override I/O bandwidth limit (in KiB/s).",
3552 default => 'migrate limit from datacenter or storage config',
3558 description
=> "the task ID.",
3563 my $rpcenv = PVE
::RPCEnvironment
::get
();
3564 my $authuser = $rpcenv->get_user();
3566 my $target = extract_param
($param, 'target');
3568 my $localnode = PVE
::INotify
::nodename
();
3569 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3571 PVE
::Cluster
::check_cfs_quorum
();
3573 PVE
::Cluster
::check_node_exists
($target);
3575 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3577 my $vmid = extract_param
($param, 'vmid');
3579 raise_param_exc
({ force
=> "Only root may use this option." })
3580 if $param->{force
} && $authuser ne 'root@pam';
3582 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3583 if $param->{migration_type
} && $authuser ne 'root@pam';
3585 # allow root only until better network permissions are available
3586 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3587 if $param->{migration_network
} && $authuser ne 'root@pam';
3590 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3592 # try to detect errors early
3594 PVE
::QemuConfig-
>check_lock($conf);
3596 if (PVE
::QemuServer
::check_running
($vmid)) {
3597 die "can't migrate running VM without --online\n" if !$param->{online
};
3599 my $repl_conf = PVE
::ReplicationConfig-
>new();
3600 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3601 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3602 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3603 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3604 "target. Use 'force' to override.\n";
3607 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3608 $param->{online
} = 0;
3611 my $storecfg = PVE
::Storage
::config
();
3613 if (my $targetstorage = $param->{targetstorage
}) {
3614 my $check_storage = sub {
3615 my ($target_sid) = @_;
3616 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3617 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3618 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3619 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3620 if !$scfg->{content
}->{images
};
3623 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3624 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3627 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3628 if !defined($storagemap->{identity
});
3630 foreach my $source (values %{$storagemap->{entries
}}) {
3631 $check_storage->($source);
3634 $check_storage->($storagemap->{default})
3635 if $storagemap->{default};
3637 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3638 if $storagemap->{identity
};
3640 $param->{storagemap
} = $storagemap;
3642 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3645 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3650 print "Requesting HA migration for VM $vmid to node $target\n";
3652 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3653 PVE
::Tools
::run_command
($cmd);
3657 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3662 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3666 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3669 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3674 __PACKAGE__-
>register_method({
3676 path
=> '{vmid}/monitor',
3680 description
=> "Execute Qemu monitor commands.",
3682 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3683 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3686 additionalProperties
=> 0,
3688 node
=> get_standard_option
('pve-node'),
3689 vmid
=> get_standard_option
('pve-vmid'),
3692 description
=> "The monitor command.",
3696 returns
=> { type
=> 'string'},
3700 my $rpcenv = PVE
::RPCEnvironment
::get
();
3701 my $authuser = $rpcenv->get_user();
3704 my $command = shift;
3705 return $command =~ m/^\s*info(\s+|$)/
3706 || $command =~ m/^\s*help\s*$/;
3709 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3710 if !&$is_ro($param->{command
});
3712 my $vmid = $param->{vmid
};
3714 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3718 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3720 $res = "ERROR: $@" if $@;
3725 __PACKAGE__-
>register_method({
3726 name
=> 'resize_vm',
3727 path
=> '{vmid}/resize',
3731 description
=> "Extend volume size.",
3733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3736 additionalProperties
=> 0,
3738 node
=> get_standard_option
('pve-node'),
3739 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3740 skiplock
=> get_standard_option
('skiplock'),
3743 description
=> "The disk you want to resize.",
3744 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3748 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3749 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.",
3753 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3759 returns
=> { type
=> 'null'},
3763 my $rpcenv = PVE
::RPCEnvironment
::get
();
3765 my $authuser = $rpcenv->get_user();
3767 my $node = extract_param
($param, 'node');
3769 my $vmid = extract_param
($param, 'vmid');
3771 my $digest = extract_param
($param, 'digest');
3773 my $disk = extract_param
($param, 'disk');
3775 my $sizestr = extract_param
($param, 'size');
3777 my $skiplock = extract_param
($param, 'skiplock');
3778 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3779 if $skiplock && $authuser ne 'root@pam';
3781 my $storecfg = PVE
::Storage
::config
();
3783 my $updatefn = sub {
3785 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3787 die "checksum missmatch (file change by other user?)\n"
3788 if $digest && $digest ne $conf->{digest
};
3789 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3791 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3793 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3795 my (undef, undef, undef, undef, undef, undef, $format) =
3796 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3798 die "can't resize volume: $disk if snapshot exists\n"
3799 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3801 my $volid = $drive->{file
};
3803 die "disk '$disk' has no associated volume\n" if !$volid;
3805 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3807 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3809 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3811 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3812 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3814 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3816 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3817 my ($ext, $newsize, $unit) = ($1, $2, $4);
3820 $newsize = $newsize * 1024;
3821 } elsif ($unit eq 'M') {
3822 $newsize = $newsize * 1024 * 1024;
3823 } elsif ($unit eq 'G') {
3824 $newsize = $newsize * 1024 * 1024 * 1024;
3825 } elsif ($unit eq 'T') {
3826 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3829 $newsize += $size if $ext;
3830 $newsize = int($newsize);
3832 die "shrinking disks is not supported\n" if $newsize < $size;
3834 return if $size == $newsize;
3836 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3838 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3840 $drive->{size
} = $newsize;
3841 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3843 PVE
::QemuConfig-
>write_config($vmid, $conf);
3846 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3850 __PACKAGE__-
>register_method({
3851 name
=> 'snapshot_list',
3852 path
=> '{vmid}/snapshot',
3854 description
=> "List all snapshots.",
3856 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3859 protected
=> 1, # qemu pid files are only readable by root
3861 additionalProperties
=> 0,
3863 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3864 node
=> get_standard_option
('pve-node'),
3873 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3877 description
=> "Snapshot includes RAM.",
3882 description
=> "Snapshot description.",
3886 description
=> "Snapshot creation time",
3888 renderer
=> 'timestamp',
3892 description
=> "Parent snapshot identifier.",
3898 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3903 my $vmid = $param->{vmid
};
3905 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3906 my $snaphash = $conf->{snapshots
} || {};
3910 foreach my $name (keys %$snaphash) {
3911 my $d = $snaphash->{$name};
3914 snaptime
=> $d->{snaptime
} || 0,
3915 vmstate
=> $d->{vmstate
} ?
1 : 0,
3916 description
=> $d->{description
} || '',
3918 $item->{parent
} = $d->{parent
} if $d->{parent
};
3919 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3923 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3926 digest
=> $conf->{digest
},
3927 running
=> $running,
3928 description
=> "You are here!",
3930 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3932 push @$res, $current;
3937 __PACKAGE__-
>register_method({
3939 path
=> '{vmid}/snapshot',
3943 description
=> "Snapshot a VM.",
3945 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3948 additionalProperties
=> 0,
3950 node
=> get_standard_option
('pve-node'),
3951 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3952 snapname
=> get_standard_option
('pve-snapshot-name'),
3956 description
=> "Save the vmstate",
3961 description
=> "A textual description or comment.",
3967 description
=> "the task ID.",
3972 my $rpcenv = PVE
::RPCEnvironment
::get
();
3974 my $authuser = $rpcenv->get_user();
3976 my $node = extract_param
($param, 'node');
3978 my $vmid = extract_param
($param, 'vmid');
3980 my $snapname = extract_param
($param, 'snapname');
3982 die "unable to use snapshot name 'current' (reserved name)\n"
3983 if $snapname eq 'current';
3985 die "unable to use snapshot name 'pending' (reserved name)\n"
3986 if lc($snapname) eq 'pending';
3989 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3990 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3991 $param->{description
});
3994 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3997 __PACKAGE__-
>register_method({
3998 name
=> 'snapshot_cmd_idx',
3999 path
=> '{vmid}/snapshot/{snapname}',
4006 additionalProperties
=> 0,
4008 vmid
=> get_standard_option
('pve-vmid'),
4009 node
=> get_standard_option
('pve-node'),
4010 snapname
=> get_standard_option
('pve-snapshot-name'),
4019 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4026 push @$res, { cmd
=> 'rollback' };
4027 push @$res, { cmd
=> 'config' };
4032 __PACKAGE__-
>register_method({
4033 name
=> 'update_snapshot_config',
4034 path
=> '{vmid}/snapshot/{snapname}/config',
4038 description
=> "Update snapshot metadata.",
4040 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4043 additionalProperties
=> 0,
4045 node
=> get_standard_option
('pve-node'),
4046 vmid
=> get_standard_option
('pve-vmid'),
4047 snapname
=> get_standard_option
('pve-snapshot-name'),
4051 description
=> "A textual description or comment.",
4055 returns
=> { type
=> 'null' },
4059 my $rpcenv = PVE
::RPCEnvironment
::get
();
4061 my $authuser = $rpcenv->get_user();
4063 my $vmid = extract_param
($param, 'vmid');
4065 my $snapname = extract_param
($param, 'snapname');
4067 return if !defined($param->{description
});
4069 my $updatefn = sub {
4071 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4073 PVE
::QemuConfig-
>check_lock($conf);
4075 my $snap = $conf->{snapshots
}->{$snapname};
4077 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4079 $snap->{description
} = $param->{description
} if defined($param->{description
});
4081 PVE
::QemuConfig-
>write_config($vmid, $conf);
4084 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4089 __PACKAGE__-
>register_method({
4090 name
=> 'get_snapshot_config',
4091 path
=> '{vmid}/snapshot/{snapname}/config',
4094 description
=> "Get snapshot configuration",
4096 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4099 additionalProperties
=> 0,
4101 node
=> get_standard_option
('pve-node'),
4102 vmid
=> get_standard_option
('pve-vmid'),
4103 snapname
=> get_standard_option
('pve-snapshot-name'),
4106 returns
=> { type
=> "object" },
4110 my $rpcenv = PVE
::RPCEnvironment
::get
();
4112 my $authuser = $rpcenv->get_user();
4114 my $vmid = extract_param
($param, 'vmid');
4116 my $snapname = extract_param
($param, 'snapname');
4118 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4120 my $snap = $conf->{snapshots
}->{$snapname};
4122 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4127 __PACKAGE__-
>register_method({
4129 path
=> '{vmid}/snapshot/{snapname}/rollback',
4133 description
=> "Rollback VM state to specified snapshot.",
4135 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4138 additionalProperties
=> 0,
4140 node
=> get_standard_option
('pve-node'),
4141 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4142 snapname
=> get_standard_option
('pve-snapshot-name'),
4147 description
=> "the task ID.",
4152 my $rpcenv = PVE
::RPCEnvironment
::get
();
4154 my $authuser = $rpcenv->get_user();
4156 my $node = extract_param
($param, 'node');
4158 my $vmid = extract_param
($param, 'vmid');
4160 my $snapname = extract_param
($param, 'snapname');
4163 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4164 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4168 # hold migration lock, this makes sure that nobody create replication snapshots
4169 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4172 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4175 __PACKAGE__-
>register_method({
4176 name
=> 'delsnapshot',
4177 path
=> '{vmid}/snapshot/{snapname}',
4181 description
=> "Delete a VM snapshot.",
4183 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4186 additionalProperties
=> 0,
4188 node
=> get_standard_option
('pve-node'),
4189 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4190 snapname
=> get_standard_option
('pve-snapshot-name'),
4194 description
=> "For removal from config file, even if removing disk snapshots fails.",
4200 description
=> "the task ID.",
4205 my $rpcenv = PVE
::RPCEnvironment
::get
();
4207 my $authuser = $rpcenv->get_user();
4209 my $node = extract_param
($param, 'node');
4211 my $vmid = extract_param
($param, 'vmid');
4213 my $snapname = extract_param
($param, 'snapname');
4216 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4217 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4220 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4223 __PACKAGE__-
>register_method({
4225 path
=> '{vmid}/template',
4229 description
=> "Create a Template.",
4231 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4232 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4235 additionalProperties
=> 0,
4237 node
=> get_standard_option
('pve-node'),
4238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4242 description
=> "If you want to convert only 1 disk to base image.",
4243 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4248 returns
=> { type
=> 'null'},
4252 my $rpcenv = PVE
::RPCEnvironment
::get
();
4254 my $authuser = $rpcenv->get_user();
4256 my $node = extract_param
($param, 'node');
4258 my $vmid = extract_param
($param, 'vmid');
4260 my $disk = extract_param
($param, 'disk');
4262 my $updatefn = sub {
4264 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4266 PVE
::QemuConfig-
>check_lock($conf);
4268 die "unable to create template, because VM contains snapshots\n"
4269 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4271 die "you can't convert a template to a template\n"
4272 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4274 die "you can't convert a VM to template if VM is running\n"
4275 if PVE
::QemuServer
::check_running
($vmid);
4278 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4281 $conf->{template
} = 1;
4282 PVE
::QemuConfig-
>write_config($vmid, $conf);
4284 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4287 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4291 __PACKAGE__-
>register_method({
4292 name
=> 'cloudinit_generated_config_dump',
4293 path
=> '{vmid}/cloudinit/dump',
4296 description
=> "Get automatically generated cloudinit config.",
4298 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4301 additionalProperties
=> 0,
4303 node
=> get_standard_option
('pve-node'),
4304 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4306 description
=> 'Config type.',
4308 enum
=> ['user', 'network', 'meta'],
4318 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4320 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});