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
1194 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $conf->{boot
})
1196 my @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
})
1197 if $bootcfg && $bootcfg->{order
};
1198 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1200 foreach my $opt (@delete) {
1201 $modified->{$opt} = 1;
1202 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1204 # value of what we want to delete, independent if pending or not
1205 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1206 if (!defined($val)) {
1207 warn "cannot delete '$opt' - not set in current configuration!\n";
1208 $modified->{$opt} = 0;
1211 my $is_pending_val = defined($conf->{pending
}->{$opt});
1212 delete $conf->{pending
}->{$opt};
1214 # remove from bootorder if necessary
1215 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1216 @bootorder = grep {$_ ne $opt} @bootorder;
1217 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1218 $modified->{boot
} = 1;
1221 if ($opt =~ m/^unused/) {
1222 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1223 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1224 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1225 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1226 delete $conf->{$opt};
1227 PVE
::QemuConfig-
>write_config($vmid, $conf);
1229 } elsif ($opt eq 'vmstate') {
1230 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1231 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1232 delete $conf->{$opt};
1233 PVE
::QemuConfig-
>write_config($vmid, $conf);
1235 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1236 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1237 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1238 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1240 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1241 PVE
::QemuConfig-
>write_config($vmid, $conf);
1242 } elsif ($opt =~ m/^serial\d+$/) {
1243 if ($val eq 'socket') {
1244 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1245 } elsif ($authuser ne 'root@pam') {
1246 die "only root can delete '$opt' config for real devices\n";
1248 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1249 PVE
::QemuConfig-
>write_config($vmid, $conf);
1250 } elsif ($opt =~ m/^usb\d+$/) {
1251 if ($val =~ m/spice/) {
1252 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1253 } elsif ($authuser ne 'root@pam') {
1254 die "only root can delete '$opt' config for real devices\n";
1256 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1257 PVE
::QemuConfig-
>write_config($vmid, $conf);
1259 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1260 PVE
::QemuConfig-
>write_config($vmid, $conf);
1264 foreach my $opt (keys %$param) { # add/change
1265 $modified->{$opt} = 1;
1266 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1267 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1269 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1271 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1272 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1273 # FIXME: cloudinit: CDROM or Disk?
1274 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1275 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1277 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1279 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1280 if defined($conf->{pending
}->{$opt});
1282 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1283 } elsif ($opt =~ m/^serial\d+/) {
1284 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1285 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1286 } elsif ($authuser ne 'root@pam') {
1287 die "only root can modify '$opt' config for real devices\n";
1289 $conf->{pending
}->{$opt} = $param->{$opt};
1290 } elsif ($opt =~ m/^usb\d+/) {
1291 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1292 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1293 } elsif ($authuser ne 'root@pam') {
1294 die "only root can modify '$opt' config for real devices\n";
1296 $conf->{pending
}->{$opt} = $param->{$opt};
1298 $conf->{pending
}->{$opt} = $param->{$opt};
1300 if ($opt eq 'boot') {
1301 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1302 if ($new_bootcfg->{order
}) {
1303 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1304 for my $dev (@devs) {
1305 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1306 my $deleted = grep {$_ eq $dev} @delete;
1307 die "invalid bootorder: device '$dev' does not exist'\n"
1308 if !$exists || $deleted;
1311 # remove legacy boot order settings if new one set
1312 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1313 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1314 if $conf->{bootdisk
};
1318 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1319 PVE
::QemuConfig-
>write_config($vmid, $conf);
1322 # remove pending changes when nothing changed
1323 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1324 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1325 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1327 return if !scalar(keys %{$conf->{pending
}});
1329 my $running = PVE
::QemuServer
::check_running
($vmid);
1331 # apply pending changes
1333 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1337 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1339 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1341 raise_param_exc
($errors) if scalar(keys %$errors);
1350 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1352 if ($background_delay) {
1354 # Note: It would be better to do that in the Event based HTTPServer
1355 # to avoid blocking call to sleep.
1357 my $end_time = time() + $background_delay;
1359 my $task = PVE
::Tools
::upid_decode
($upid);
1362 while (time() < $end_time) {
1363 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1365 sleep(1); # this gets interrupted when child process ends
1369 my $status = PVE
::Tools
::upid_read_status
($upid);
1370 return undef if $status eq 'OK';
1379 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1382 my $vm_config_perm_list = [
1387 'VM.Config.Network',
1389 'VM.Config.Options',
1390 'VM.Config.Cloudinit',
1393 __PACKAGE__-
>register_method({
1394 name
=> 'update_vm_async',
1395 path
=> '{vmid}/config',
1399 description
=> "Set virtual machine options (asynchrounous API).",
1401 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1404 additionalProperties
=> 0,
1405 properties
=> PVE
::QemuServer
::json_config_properties
(
1407 node
=> get_standard_option
('pve-node'),
1408 vmid
=> get_standard_option
('pve-vmid'),
1409 skiplock
=> get_standard_option
('skiplock'),
1411 type
=> 'string', format
=> 'pve-configid-list',
1412 description
=> "A list of settings you want to delete.",
1416 type
=> 'string', format
=> 'pve-configid-list',
1417 description
=> "Revert a pending change.",
1422 description
=> $opt_force_description,
1424 requires
=> 'delete',
1428 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1432 background_delay
=> {
1434 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1445 code
=> $update_vm_api,
1448 __PACKAGE__-
>register_method({
1449 name
=> 'update_vm',
1450 path
=> '{vmid}/config',
1454 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1456 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1459 additionalProperties
=> 0,
1460 properties
=> PVE
::QemuServer
::json_config_properties
(
1462 node
=> get_standard_option
('pve-node'),
1463 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1464 skiplock
=> get_standard_option
('skiplock'),
1466 type
=> 'string', format
=> 'pve-configid-list',
1467 description
=> "A list of settings you want to delete.",
1471 type
=> 'string', format
=> 'pve-configid-list',
1472 description
=> "Revert a pending change.",
1477 description
=> $opt_force_description,
1479 requires
=> 'delete',
1483 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1489 returns
=> { type
=> 'null' },
1492 &$update_vm_api($param, 1);
1497 __PACKAGE__-
>register_method({
1498 name
=> 'destroy_vm',
1503 description
=> "Destroy the vm (also delete all used/owned volumes).",
1505 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1508 additionalProperties
=> 0,
1510 node
=> get_standard_option
('pve-node'),
1511 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1512 skiplock
=> get_standard_option
('skiplock'),
1515 description
=> "Remove vmid from backup cron jobs.",
1526 my $rpcenv = PVE
::RPCEnvironment
::get
();
1527 my $authuser = $rpcenv->get_user();
1528 my $vmid = $param->{vmid
};
1530 my $skiplock = $param->{skiplock
};
1531 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1532 if $skiplock && $authuser ne 'root@pam';
1534 my $early_checks = sub {
1536 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1537 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1539 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1541 if (!$param->{purge
}) {
1542 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1544 # don't allow destroy if with replication jobs but no purge param
1545 my $repl_conf = PVE
::ReplicationConfig-
>new();
1546 $repl_conf->check_for_existing_jobs($vmid);
1549 die "VM $vmid is running - destroy failed\n"
1550 if PVE
::QemuServer
::check_running
($vmid);
1560 my $storecfg = PVE
::Storage
::config
();
1562 syslog
('info', "destroy VM $vmid: $upid\n");
1563 PVE
::QemuConfig-
>lock_config($vmid, sub {
1564 # repeat, config might have changed
1565 my $ha_managed = $early_checks->();
1567 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1569 PVE
::AccessControl
::remove_vm_access
($vmid);
1570 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1571 if ($param->{purge
}) {
1572 print "purging VM $vmid from related configurations..\n";
1573 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1574 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1577 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1578 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1582 # only now remove the zombie config, else we can have reuse race
1583 PVE
::QemuConfig-
>destroy_config($vmid);
1587 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1590 __PACKAGE__-
>register_method({
1592 path
=> '{vmid}/unlink',
1596 description
=> "Unlink/delete disk images.",
1598 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1601 additionalProperties
=> 0,
1603 node
=> get_standard_option
('pve-node'),
1604 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1606 type
=> 'string', format
=> 'pve-configid-list',
1607 description
=> "A list of disk IDs you want to delete.",
1611 description
=> $opt_force_description,
1616 returns
=> { type
=> 'null'},
1620 $param->{delete} = extract_param
($param, 'idlist');
1622 __PACKAGE__-
>update_vm($param);
1627 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1628 my $gen_rand_chars = sub {
1631 die "invalid length $length" if $length < 1;
1633 my $min = ord('!'); # first printable ascii
1635 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1636 die "failed to generate random bytes!\n"
1639 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1646 __PACKAGE__-
>register_method({
1648 path
=> '{vmid}/vncproxy',
1652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1654 description
=> "Creates a TCP VNC proxy connections.",
1656 additionalProperties
=> 0,
1658 node
=> get_standard_option
('pve-node'),
1659 vmid
=> get_standard_option
('pve-vmid'),
1663 description
=> "starts websockify instead of vncproxy",
1665 'generate-password' => {
1669 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1674 additionalProperties
=> 0,
1676 user
=> { type
=> 'string' },
1677 ticket
=> { type
=> 'string' },
1680 description
=> "Returned if requested with 'generate-password' param."
1681 ." Consists of printable ASCII characters ('!' .. '~').",
1684 cert
=> { type
=> 'string' },
1685 port
=> { type
=> 'integer' },
1686 upid
=> { type
=> 'string' },
1692 my $rpcenv = PVE
::RPCEnvironment
::get
();
1694 my $authuser = $rpcenv->get_user();
1696 my $vmid = $param->{vmid
};
1697 my $node = $param->{node
};
1698 my $websocket = $param->{websocket
};
1700 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1704 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1705 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1708 my $authpath = "/vms/$vmid";
1710 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1711 my $password = $ticket;
1712 if ($param->{'generate-password'}) {
1713 $password = $gen_rand_chars->(8);
1716 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1722 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1723 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1724 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1725 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1726 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1728 $family = PVE
::Tools
::get_host_address_family
($node);
1731 my $port = PVE
::Tools
::next_vnc_port
($family);
1738 syslog
('info', "starting vnc proxy $upid\n");
1742 if (defined($serial)) {
1744 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1746 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1747 '-timeout', $timeout, '-authpath', $authpath,
1748 '-perm', 'Sys.Console'];
1750 if ($param->{websocket
}) {
1751 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1752 push @$cmd, '-notls', '-listen', 'localhost';
1755 push @$cmd, '-c', @$remcmd, @$termcmd;
1757 PVE
::Tools
::run_command
($cmd);
1761 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1763 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1765 my $sock = IO
::Socket
::IP-
>new(
1770 GetAddrInfoFlags
=> 0,
1771 ) or die "failed to create socket: $!\n";
1772 # Inside the worker we shouldn't have any previous alarms
1773 # running anyway...:
1775 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1777 accept(my $cli, $sock) or die "connection failed: $!\n";
1780 if (PVE
::Tools
::run_command
($cmd,
1781 output
=> '>&'.fileno($cli),
1782 input
=> '<&'.fileno($cli),
1785 die "Failed to run vncproxy.\n";
1792 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1794 PVE
::Tools
::wait_for_vnc_port
($port);
1803 $res->{password
} = $password if $param->{'generate-password'};
1808 __PACKAGE__-
>register_method({
1809 name
=> 'termproxy',
1810 path
=> '{vmid}/termproxy',
1814 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1816 description
=> "Creates a TCP proxy connections.",
1818 additionalProperties
=> 0,
1820 node
=> get_standard_option
('pve-node'),
1821 vmid
=> get_standard_option
('pve-vmid'),
1825 enum
=> [qw(serial0 serial1 serial2 serial3)],
1826 description
=> "opens a serial terminal (defaults to display)",
1831 additionalProperties
=> 0,
1833 user
=> { type
=> 'string' },
1834 ticket
=> { type
=> 'string' },
1835 port
=> { type
=> 'integer' },
1836 upid
=> { type
=> 'string' },
1842 my $rpcenv = PVE
::RPCEnvironment
::get
();
1844 my $authuser = $rpcenv->get_user();
1846 my $vmid = $param->{vmid
};
1847 my $node = $param->{node
};
1848 my $serial = $param->{serial
};
1850 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1852 if (!defined($serial)) {
1854 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1855 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1859 my $authpath = "/vms/$vmid";
1861 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1866 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1867 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1868 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1869 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1870 push @$remcmd, '--';
1872 $family = PVE
::Tools
::get_host_address_family
($node);
1875 my $port = PVE
::Tools
::next_vnc_port
($family);
1877 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1878 push @$termcmd, '-iface', $serial if $serial;
1883 syslog
('info', "starting qemu termproxy $upid\n");
1885 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1886 '--perm', 'VM.Console', '--'];
1887 push @$cmd, @$remcmd, @$termcmd;
1889 PVE
::Tools
::run_command
($cmd);
1892 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1894 PVE
::Tools
::wait_for_vnc_port
($port);
1904 __PACKAGE__-
>register_method({
1905 name
=> 'vncwebsocket',
1906 path
=> '{vmid}/vncwebsocket',
1909 description
=> "You also need to pass a valid ticket (vncticket).",
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1912 description
=> "Opens a weksocket for VNC traffic.",
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1917 vmid
=> get_standard_option
('pve-vmid'),
1919 description
=> "Ticket from previous call to vncproxy.",
1924 description
=> "Port number returned by previous vncproxy call.",
1934 port
=> { type
=> 'string' },
1940 my $rpcenv = PVE
::RPCEnvironment
::get
();
1942 my $authuser = $rpcenv->get_user();
1944 my $vmid = $param->{vmid
};
1945 my $node = $param->{node
};
1947 my $authpath = "/vms/$vmid";
1949 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1951 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1953 # Note: VNC ports are acessible from outside, so we do not gain any
1954 # security if we verify that $param->{port} belongs to VM $vmid. This
1955 # check is done by verifying the VNC ticket (inside VNC protocol).
1957 my $port = $param->{port
};
1959 return { port
=> $port };
1962 __PACKAGE__-
>register_method({
1963 name
=> 'spiceproxy',
1964 path
=> '{vmid}/spiceproxy',
1969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1971 description
=> "Returns a SPICE configuration to connect to the VM.",
1973 additionalProperties
=> 0,
1975 node
=> get_standard_option
('pve-node'),
1976 vmid
=> get_standard_option
('pve-vmid'),
1977 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1980 returns
=> get_standard_option
('remote-viewer-config'),
1984 my $rpcenv = PVE
::RPCEnvironment
::get
();
1986 my $authuser = $rpcenv->get_user();
1988 my $vmid = $param->{vmid
};
1989 my $node = $param->{node
};
1990 my $proxy = $param->{proxy
};
1992 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1993 my $title = "VM $vmid";
1994 $title .= " - ". $conf->{name
} if $conf->{name
};
1996 my $port = PVE
::QemuServer
::spice_port
($vmid);
1998 my ($ticket, undef, $remote_viewer_config) =
1999 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2001 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2002 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2004 return $remote_viewer_config;
2007 __PACKAGE__-
>register_method({
2009 path
=> '{vmid}/status',
2012 description
=> "Directory index",
2017 additionalProperties
=> 0,
2019 node
=> get_standard_option
('pve-node'),
2020 vmid
=> get_standard_option
('pve-vmid'),
2028 subdir
=> { type
=> 'string' },
2031 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2037 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2040 { subdir
=> 'current' },
2041 { subdir
=> 'start' },
2042 { subdir
=> 'stop' },
2043 { subdir
=> 'reset' },
2044 { subdir
=> 'shutdown' },
2045 { subdir
=> 'suspend' },
2046 { subdir
=> 'reboot' },
2052 __PACKAGE__-
>register_method({
2053 name
=> 'vm_status',
2054 path
=> '{vmid}/status/current',
2057 protected
=> 1, # qemu pid files are only readable by root
2058 description
=> "Get virtual machine status.",
2060 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2063 additionalProperties
=> 0,
2065 node
=> get_standard_option
('pve-node'),
2066 vmid
=> get_standard_option
('pve-vmid'),
2072 %$PVE::QemuServer
::vmstatus_return_properties
,
2074 description
=> "HA manager service status.",
2078 description
=> "Qemu VGA configuration supports spice.",
2083 description
=> "Qemu GuestAgent enabled in config.",
2093 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2095 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2096 my $status = $vmstatus->{$param->{vmid
}};
2098 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2100 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2101 $status->{agent
} = 1 if (PVE
::QemuServer
::parse_guest_agent
($conf)->{enabled
});
2106 __PACKAGE__-
>register_method({
2108 path
=> '{vmid}/status/start',
2112 description
=> "Start virtual machine.",
2114 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2117 additionalProperties
=> 0,
2119 node
=> get_standard_option
('pve-node'),
2120 vmid
=> get_standard_option
('pve-vmid',
2121 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2122 skiplock
=> get_standard_option
('skiplock'),
2123 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2124 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2127 enum
=> ['secure', 'insecure'],
2128 description
=> "Migration traffic is encrypted using an SSH " .
2129 "tunnel by default. On secure, completely private networks " .
2130 "this can be disabled to increase performance.",
2133 migration_network
=> {
2134 type
=> 'string', format
=> 'CIDR',
2135 description
=> "CIDR of the (sub) network that is used for migration.",
2138 machine
=> get_standard_option
('pve-qemu-machine'),
2140 description
=> "Override QEMU's -cpu argument with the given string.",
2144 targetstorage
=> get_standard_option
('pve-targetstorage'),
2146 description
=> "Wait maximal timeout seconds.",
2149 default => 'max(30, vm memory in GiB)',
2160 my $rpcenv = PVE
::RPCEnvironment
::get
();
2161 my $authuser = $rpcenv->get_user();
2163 my $node = extract_param
($param, 'node');
2164 my $vmid = extract_param
($param, 'vmid');
2165 my $timeout = extract_param
($param, 'timeout');
2167 my $machine = extract_param
($param, 'machine');
2168 my $force_cpu = extract_param
($param, 'force-cpu');
2170 my $get_root_param = sub {
2171 my $value = extract_param
($param, $_[0]);
2172 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2173 if $value && $authuser ne 'root@pam';
2177 my $stateuri = $get_root_param->('stateuri');
2178 my $skiplock = $get_root_param->('skiplock');
2179 my $migratedfrom = $get_root_param->('migratedfrom');
2180 my $migration_type = $get_root_param->('migration_type');
2181 my $migration_network = $get_root_param->('migration_network');
2182 my $targetstorage = $get_root_param->('targetstorage');
2186 if ($targetstorage) {
2187 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2189 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2190 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2194 # read spice ticket from STDIN
2196 my $nbd_protocol_version = 0;
2197 my $replicated_volumes = {};
2198 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2199 while (defined(my $line = <STDIN
>)) {
2201 if ($line =~ m/^spice_ticket: (.+)$/) {
2203 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2204 $nbd_protocol_version = $1;
2205 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2206 $replicated_volumes->{$1} = 1;
2208 # fallback for old source node
2209 $spice_ticket = $line;
2214 PVE
::Cluster
::check_cfs_quorum
();
2216 my $storecfg = PVE
::Storage
::config
();
2218 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2222 print "Requesting HA start for VM $vmid\n";
2224 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2225 PVE
::Tools
::run_command
($cmd);
2229 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2236 syslog
('info', "start VM $vmid: $upid\n");
2238 my $migrate_opts = {
2239 migratedfrom
=> $migratedfrom,
2240 spice_ticket
=> $spice_ticket,
2241 network
=> $migration_network,
2242 type
=> $migration_type,
2243 storagemap
=> $storagemap,
2244 nbd_proto_version
=> $nbd_protocol_version,
2245 replicated_volumes
=> $replicated_volumes,
2249 statefile
=> $stateuri,
2250 skiplock
=> $skiplock,
2251 forcemachine
=> $machine,
2252 timeout
=> $timeout,
2253 forcecpu
=> $force_cpu,
2256 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2260 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2264 __PACKAGE__-
>register_method({
2266 path
=> '{vmid}/status/stop',
2270 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2271 "is akin to pulling the power plug of a running computer and may damage the VM data",
2273 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2276 additionalProperties
=> 0,
2278 node
=> get_standard_option
('pve-node'),
2279 vmid
=> get_standard_option
('pve-vmid',
2280 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2281 skiplock
=> get_standard_option
('skiplock'),
2282 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2284 description
=> "Wait maximal timeout seconds.",
2290 description
=> "Do not deactivate storage volumes.",
2303 my $rpcenv = PVE
::RPCEnvironment
::get
();
2304 my $authuser = $rpcenv->get_user();
2306 my $node = extract_param
($param, 'node');
2307 my $vmid = extract_param
($param, 'vmid');
2309 my $skiplock = extract_param
($param, 'skiplock');
2310 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2311 if $skiplock && $authuser ne 'root@pam';
2313 my $keepActive = extract_param
($param, 'keepActive');
2314 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2315 if $keepActive && $authuser ne 'root@pam';
2317 my $migratedfrom = extract_param
($param, 'migratedfrom');
2318 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2319 if $migratedfrom && $authuser ne 'root@pam';
2322 my $storecfg = PVE
::Storage
::config
();
2324 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2329 print "Requesting HA stop for VM $vmid\n";
2331 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2332 PVE
::Tools
::run_command
($cmd);
2336 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2342 syslog
('info', "stop VM $vmid: $upid\n");
2344 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2345 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2349 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2353 __PACKAGE__-
>register_method({
2355 path
=> '{vmid}/status/reset',
2359 description
=> "Reset virtual machine.",
2361 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2364 additionalProperties
=> 0,
2366 node
=> get_standard_option
('pve-node'),
2367 vmid
=> get_standard_option
('pve-vmid',
2368 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2369 skiplock
=> get_standard_option
('skiplock'),
2378 my $rpcenv = PVE
::RPCEnvironment
::get
();
2380 my $authuser = $rpcenv->get_user();
2382 my $node = extract_param
($param, 'node');
2384 my $vmid = extract_param
($param, 'vmid');
2386 my $skiplock = extract_param
($param, 'skiplock');
2387 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2388 if $skiplock && $authuser ne 'root@pam';
2390 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2395 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2400 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2403 __PACKAGE__-
>register_method({
2404 name
=> 'vm_shutdown',
2405 path
=> '{vmid}/status/shutdown',
2409 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2410 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2412 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2415 additionalProperties
=> 0,
2417 node
=> get_standard_option
('pve-node'),
2418 vmid
=> get_standard_option
('pve-vmid',
2419 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2420 skiplock
=> get_standard_option
('skiplock'),
2422 description
=> "Wait maximal timeout seconds.",
2428 description
=> "Make sure the VM stops.",
2434 description
=> "Do not deactivate storage volumes.",
2447 my $rpcenv = PVE
::RPCEnvironment
::get
();
2448 my $authuser = $rpcenv->get_user();
2450 my $node = extract_param
($param, 'node');
2451 my $vmid = extract_param
($param, 'vmid');
2453 my $skiplock = extract_param
($param, 'skiplock');
2454 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2455 if $skiplock && $authuser ne 'root@pam';
2457 my $keepActive = extract_param
($param, 'keepActive');
2458 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2459 if $keepActive && $authuser ne 'root@pam';
2461 my $storecfg = PVE
::Storage
::config
();
2465 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2466 # otherwise, we will infer a shutdown command, but run into the timeout,
2467 # then when the vm is resumed, it will instantly shutdown
2469 # checking the qmp status here to get feedback to the gui/cli/api
2470 # and the status query should not take too long
2471 my $qmpstatus = eval {
2472 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2473 mon_cmd
($vmid, "query-status");
2477 if (!$err && $qmpstatus->{status
} eq "paused") {
2478 if ($param->{forceStop
}) {
2479 warn "VM is paused - stop instead of shutdown\n";
2482 die "VM is paused - cannot shutdown\n";
2486 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2488 my $timeout = $param->{timeout
} // 60;
2492 print "Requesting HA stop for VM $vmid\n";
2494 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2495 PVE
::Tools
::run_command
($cmd);
2499 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2506 syslog
('info', "shutdown VM $vmid: $upid\n");
2508 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2509 $shutdown, $param->{forceStop
}, $keepActive);
2513 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2517 __PACKAGE__-
>register_method({
2518 name
=> 'vm_reboot',
2519 path
=> '{vmid}/status/reboot',
2523 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2525 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2528 additionalProperties
=> 0,
2530 node
=> get_standard_option
('pve-node'),
2531 vmid
=> get_standard_option
('pve-vmid',
2532 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2534 description
=> "Wait maximal timeout seconds for the shutdown.",
2547 my $rpcenv = PVE
::RPCEnvironment
::get
();
2548 my $authuser = $rpcenv->get_user();
2550 my $node = extract_param
($param, 'node');
2551 my $vmid = extract_param
($param, 'vmid');
2553 my $qmpstatus = eval {
2554 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2555 mon_cmd
($vmid, "query-status");
2559 if (!$err && $qmpstatus->{status
} eq "paused") {
2560 die "VM is paused - cannot shutdown\n";
2563 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2568 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2569 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2573 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2576 __PACKAGE__-
>register_method({
2577 name
=> 'vm_suspend',
2578 path
=> '{vmid}/status/suspend',
2582 description
=> "Suspend virtual machine.",
2584 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2585 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2586 " on the storage for the vmstate.",
2587 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2590 additionalProperties
=> 0,
2592 node
=> get_standard_option
('pve-node'),
2593 vmid
=> get_standard_option
('pve-vmid',
2594 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2595 skiplock
=> get_standard_option
('skiplock'),
2600 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2602 statestorage
=> get_standard_option
('pve-storage-id', {
2603 description
=> "The storage for the VM state",
2604 requires
=> 'todisk',
2606 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2616 my $rpcenv = PVE
::RPCEnvironment
::get
();
2617 my $authuser = $rpcenv->get_user();
2619 my $node = extract_param
($param, 'node');
2620 my $vmid = extract_param
($param, 'vmid');
2622 my $todisk = extract_param
($param, 'todisk') // 0;
2624 my $statestorage = extract_param
($param, 'statestorage');
2626 my $skiplock = extract_param
($param, 'skiplock');
2627 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2628 if $skiplock && $authuser ne 'root@pam';
2630 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2632 die "Cannot suspend HA managed VM to disk\n"
2633 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2635 # early check for storage permission, for better user feedback
2637 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2639 if (!$statestorage) {
2640 # get statestorage from config if none is given
2641 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2642 my $storecfg = PVE
::Storage
::config
();
2643 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2646 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2652 syslog
('info', "suspend VM $vmid: $upid\n");
2654 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2659 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2660 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2663 __PACKAGE__-
>register_method({
2664 name
=> 'vm_resume',
2665 path
=> '{vmid}/status/resume',
2669 description
=> "Resume virtual machine.",
2671 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2674 additionalProperties
=> 0,
2676 node
=> get_standard_option
('pve-node'),
2677 vmid
=> get_standard_option
('pve-vmid',
2678 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2679 skiplock
=> get_standard_option
('skiplock'),
2680 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2690 my $rpcenv = PVE
::RPCEnvironment
::get
();
2692 my $authuser = $rpcenv->get_user();
2694 my $node = extract_param
($param, 'node');
2696 my $vmid = extract_param
($param, 'vmid');
2698 my $skiplock = extract_param
($param, 'skiplock');
2699 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2700 if $skiplock && $authuser ne 'root@pam';
2702 my $nocheck = extract_param
($param, 'nocheck');
2703 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2704 if $nocheck && $authuser ne 'root@pam';
2706 my $to_disk_suspended;
2708 PVE
::QemuConfig-
>lock_config($vmid, sub {
2709 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2710 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2714 die "VM $vmid not running\n"
2715 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2720 syslog
('info', "resume VM $vmid: $upid\n");
2722 if (!$to_disk_suspended) {
2723 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2725 my $storecfg = PVE
::Storage
::config
();
2726 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2732 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2735 __PACKAGE__-
>register_method({
2736 name
=> 'vm_sendkey',
2737 path
=> '{vmid}/sendkey',
2741 description
=> "Send key event to virtual machine.",
2743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2746 additionalProperties
=> 0,
2748 node
=> get_standard_option
('pve-node'),
2749 vmid
=> get_standard_option
('pve-vmid',
2750 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2751 skiplock
=> get_standard_option
('skiplock'),
2753 description
=> "The key (qemu monitor encoding).",
2758 returns
=> { type
=> 'null'},
2762 my $rpcenv = PVE
::RPCEnvironment
::get
();
2764 my $authuser = $rpcenv->get_user();
2766 my $node = extract_param
($param, 'node');
2768 my $vmid = extract_param
($param, 'vmid');
2770 my $skiplock = extract_param
($param, 'skiplock');
2771 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2772 if $skiplock && $authuser ne 'root@pam';
2774 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2779 __PACKAGE__-
>register_method({
2780 name
=> 'vm_feature',
2781 path
=> '{vmid}/feature',
2785 description
=> "Check if feature for virtual machine is available.",
2787 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2790 additionalProperties
=> 0,
2792 node
=> get_standard_option
('pve-node'),
2793 vmid
=> get_standard_option
('pve-vmid'),
2795 description
=> "Feature to check.",
2797 enum
=> [ 'snapshot', 'clone', 'copy' ],
2799 snapname
=> get_standard_option
('pve-snapshot-name', {
2807 hasFeature
=> { type
=> 'boolean' },
2810 items
=> { type
=> 'string' },
2817 my $node = extract_param
($param, 'node');
2819 my $vmid = extract_param
($param, 'vmid');
2821 my $snapname = extract_param
($param, 'snapname');
2823 my $feature = extract_param
($param, 'feature');
2825 my $running = PVE
::QemuServer
::check_running
($vmid);
2827 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2830 my $snap = $conf->{snapshots
}->{$snapname};
2831 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2834 my $storecfg = PVE
::Storage
::config
();
2836 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2837 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2840 hasFeature
=> $hasFeature,
2841 nodes
=> [ keys %$nodelist ],
2845 __PACKAGE__-
>register_method({
2847 path
=> '{vmid}/clone',
2851 description
=> "Create a copy of virtual machine/template.",
2853 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2854 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2855 "'Datastore.AllocateSpace' on any used storage.",
2858 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2860 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2861 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2866 additionalProperties
=> 0,
2868 node
=> get_standard_option
('pve-node'),
2869 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2870 newid
=> get_standard_option
('pve-vmid', {
2871 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2872 description
=> 'VMID for the clone.' }),
2875 type
=> 'string', format
=> 'dns-name',
2876 description
=> "Set a name for the new VM.",
2881 description
=> "Description for the new VM.",
2885 type
=> 'string', format
=> 'pve-poolid',
2886 description
=> "Add the new VM to the specified pool.",
2888 snapname
=> get_standard_option
('pve-snapshot-name', {
2891 storage
=> get_standard_option
('pve-storage-id', {
2892 description
=> "Target storage for full clone.",
2896 description
=> "Target format for file storage. Only valid for full clone.",
2899 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2904 description
=> "Create a full copy of all disks. This is always done when " .
2905 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2907 target
=> get_standard_option
('pve-node', {
2908 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2912 description
=> "Override I/O bandwidth limit (in KiB/s).",
2916 default => 'clone limit from datacenter or storage config',
2926 my $rpcenv = PVE
::RPCEnvironment
::get
();
2927 my $authuser = $rpcenv->get_user();
2929 my $node = extract_param
($param, 'node');
2930 my $vmid = extract_param
($param, 'vmid');
2931 my $newid = extract_param
($param, 'newid');
2932 my $pool = extract_param
($param, 'pool');
2933 $rpcenv->check_pool_exist($pool) if defined($pool);
2935 my $snapname = extract_param
($param, 'snapname');
2936 my $storage = extract_param
($param, 'storage');
2937 my $format = extract_param
($param, 'format');
2938 my $target = extract_param
($param, 'target');
2940 my $localnode = PVE
::INotify
::nodename
();
2942 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2946 PVE
::Cluster
::check_node_exists
($target) if $target;
2948 my $storecfg = PVE
::Storage
::config
();
2951 # check if storage is enabled on local node
2952 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2954 # check if storage is available on target node
2955 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2956 # clone only works if target storage is shared
2957 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2958 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2962 PVE
::Cluster
::check_cfs_quorum
();
2964 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2967 # do all tests after lock but before forking worker - if possible
2969 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2970 PVE
::QemuConfig-
>check_lock($conf);
2972 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2973 die "unexpected state change\n" if $verify_running != $running;
2975 die "snapshot '$snapname' does not exist\n"
2976 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2978 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2980 die "parameter 'storage' not allowed for linked clones\n"
2981 if defined($storage) && !$full;
2983 die "parameter 'format' not allowed for linked clones\n"
2984 if defined($format) && !$full;
2986 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2988 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2990 die "can't clone VM to node '$target' (VM uses local storage)\n"
2991 if $target && !$sharedvm;
2993 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2994 die "unable to create VM $newid: config file already exists\n"
2997 my $newconf = { lock => 'clone' };
3002 foreach my $opt (keys %$oldconf) {
3003 my $value = $oldconf->{$opt};
3005 # do not copy snapshot related info
3006 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3007 $opt eq 'vmstate' || $opt eq 'snapstate';
3009 # no need to copy unused images, because VMID(owner) changes anyways
3010 next if $opt =~ m/^unused\d+$/;
3012 # always change MAC! address
3013 if ($opt =~ m/^net(\d+)$/) {
3014 my $net = PVE
::QemuServer
::parse_net
($value);
3015 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3016 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3017 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3018 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3019 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3020 die "unable to parse drive options for '$opt'\n" if !$drive;
3021 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3022 $newconf->{$opt} = $value; # simply copy configuration
3024 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3025 die "Full clone feature is not supported for drive '$opt'\n"
3026 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3027 $fullclone->{$opt} = 1;
3029 # not full means clone instead of copy
3030 die "Linked clone feature is not supported for drive '$opt'\n"
3031 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3033 $drives->{$opt} = $drive;
3034 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3035 push @$vollist, $drive->{file
};
3038 # copy everything else
3039 $newconf->{$opt} = $value;
3043 # auto generate a new uuid
3044 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3045 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3046 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3047 # auto generate a new vmgenid only if the option was set for template
3048 if ($newconf->{vmgenid
}) {
3049 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3052 delete $newconf->{template
};
3054 if ($param->{name
}) {
3055 $newconf->{name
} = $param->{name
};
3057 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3060 if ($param->{description
}) {
3061 $newconf->{description
} = $param->{description
};
3064 # create empty/temp config - this fails if VM already exists on other node
3065 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3066 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3071 my $newvollist = [];
3078 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3080 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3082 my $bwlimit = extract_param
($param, 'bwlimit');
3084 my $total_jobs = scalar(keys %{$drives});
3087 foreach my $opt (keys %$drives) {
3088 my $drive = $drives->{$opt};
3089 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3090 my $completion = $skipcomplete ?
'skip' : 'complete';
3092 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3093 my $storage_list = [ $src_sid ];
3094 push @$storage_list, $storage if defined($storage);
3095 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3097 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3098 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3099 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3101 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3103 PVE
::QemuConfig-
>write_config($newid, $newconf);
3107 delete $newconf->{lock};
3109 # do not write pending changes
3110 if (my @changes = keys %{$newconf->{pending
}}) {
3111 my $pending = join(',', @changes);
3112 warn "found pending changes for '$pending', discarding for clone\n";
3113 delete $newconf->{pending
};
3116 PVE
::QemuConfig-
>write_config($newid, $newconf);
3119 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3120 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3121 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3123 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3124 die "Failed to move config to node '$target' - rename failed: $!\n"
3125 if !rename($conffile, $newconffile);
3128 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3131 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3132 sleep 1; # some storage like rbd need to wait before release volume - really?
3134 foreach my $volid (@$newvollist) {
3135 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3139 PVE
::Firewall
::remove_vmfw_conf
($newid);
3141 unlink $conffile; # avoid races -> last thing before die
3143 die "clone failed: $err";
3149 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3151 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3154 # Aquire exclusive lock lock for $newid
3155 my $lock_target_vm = sub {
3156 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3159 # exclusive lock if VM is running - else shared lock is enough;
3161 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3163 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3167 __PACKAGE__-
>register_method({
3168 name
=> 'move_vm_disk',
3169 path
=> '{vmid}/move_disk',
3173 description
=> "Move volume to different storage.",
3175 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3177 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3178 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3182 additionalProperties
=> 0,
3184 node
=> get_standard_option
('pve-node'),
3185 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3188 description
=> "The disk you want to move.",
3189 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3191 storage
=> get_standard_option
('pve-storage-id', {
3192 description
=> "Target storage.",
3193 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3197 description
=> "Target Format.",
3198 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3203 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3209 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3214 description
=> "Override I/O bandwidth limit (in KiB/s).",
3218 default => 'move limit from datacenter or storage config',
3224 description
=> "the task ID.",
3229 my $rpcenv = PVE
::RPCEnvironment
::get
();
3230 my $authuser = $rpcenv->get_user();
3232 my $node = extract_param
($param, 'node');
3233 my $vmid = extract_param
($param, 'vmid');
3234 my $digest = extract_param
($param, 'digest');
3235 my $disk = extract_param
($param, 'disk');
3236 my $storeid = extract_param
($param, 'storage');
3237 my $format = extract_param
($param, 'format');
3239 my $storecfg = PVE
::Storage
::config
();
3241 my $updatefn = sub {
3242 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3243 PVE
::QemuConfig-
>check_lock($conf);
3245 die "VM config checksum missmatch (file change by other user?)\n"
3246 if $digest && $digest ne $conf->{digest
};
3248 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3250 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3252 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3253 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3255 my $old_volid = $drive->{file
};
3257 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3258 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3262 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3263 (!$format || !$oldfmt || $oldfmt eq $format);
3265 # this only checks snapshots because $disk is passed!
3266 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3267 die "you can't move a disk with snapshots and delete the source\n"
3268 if $snapshotted && $param->{delete};
3270 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3272 my $running = PVE
::QemuServer
::check_running
($vmid);
3274 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3277 my $newvollist = [];
3283 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3285 warn "moving disk with snapshots, snapshots will not be moved!\n"
3288 my $bwlimit = extract_param
($param, 'bwlimit');
3289 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3291 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3292 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3294 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3296 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3298 # convert moved disk to base if part of template
3299 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3300 if PVE
::QemuConfig-
>is_template($conf);
3302 PVE
::QemuConfig-
>write_config($vmid, $conf);
3304 my $do_trim = PVE
::QemuServer
::parse_guest_agent
($conf)->{fstrim_cloned_disks
};
3305 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3306 eval { mon_cmd
($vmid, "guest-fstrim") };
3310 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3311 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3317 foreach my $volid (@$newvollist) {
3318 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3321 die "storage migration failed: $err";
3324 if ($param->{delete}) {
3326 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3327 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3333 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3336 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3339 my $check_vm_disks_local = sub {
3340 my ($storecfg, $vmconf, $vmid) = @_;
3342 my $local_disks = {};
3344 # add some more information to the disks e.g. cdrom
3345 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3346 my ($volid, $attr) = @_;
3348 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3350 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3351 return if $scfg->{shared
};
3353 # The shared attr here is just a special case where the vdisk
3354 # is marked as shared manually
3355 return if $attr->{shared
};
3356 return if $attr->{cdrom
} and $volid eq "none";
3358 if (exists $local_disks->{$volid}) {
3359 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3361 $local_disks->{$volid} = $attr;
3362 # ensure volid is present in case it's needed
3363 $local_disks->{$volid}->{volid
} = $volid;
3367 return $local_disks;
3370 __PACKAGE__-
>register_method({
3371 name
=> 'migrate_vm_precondition',
3372 path
=> '{vmid}/migrate',
3376 description
=> "Get preconditions for migration.",
3378 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3381 additionalProperties
=> 0,
3383 node
=> get_standard_option
('pve-node'),
3384 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3385 target
=> get_standard_option
('pve-node', {
3386 description
=> "Target node.",
3387 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3395 running
=> { type
=> 'boolean' },
3399 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3401 not_allowed_nodes
=> {
3404 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3408 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3410 local_resources
=> {
3412 description
=> "List local resources e.g. pci, usb"
3419 my $rpcenv = PVE
::RPCEnvironment
::get
();
3421 my $authuser = $rpcenv->get_user();
3423 PVE
::Cluster
::check_cfs_quorum
();
3427 my $vmid = extract_param
($param, 'vmid');
3428 my $target = extract_param
($param, 'target');
3429 my $localnode = PVE
::INotify
::nodename
();
3433 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3434 my $storecfg = PVE
::Storage
::config
();
3437 # try to detect errors early
3438 PVE
::QemuConfig-
>check_lock($vmconf);
3440 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3442 # if vm is not running, return target nodes where local storage is available
3443 # for offline migration
3444 if (!$res->{running
}) {
3445 $res->{allowed_nodes
} = [];
3446 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3447 delete $checked_nodes->{$localnode};
3449 foreach my $node (keys %$checked_nodes) {
3450 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3451 push @{$res->{allowed_nodes
}}, $node;
3455 $res->{not_allowed_nodes
} = $checked_nodes;
3459 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3460 $res->{local_disks
} = [ values %$local_disks ];;
3462 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3464 $res->{local_resources
} = $local_resources;
3471 __PACKAGE__-
>register_method({
3472 name
=> 'migrate_vm',
3473 path
=> '{vmid}/migrate',
3477 description
=> "Migrate virtual machine. Creates a new migration task.",
3479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3482 additionalProperties
=> 0,
3484 node
=> get_standard_option
('pve-node'),
3485 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3486 target
=> get_standard_option
('pve-node', {
3487 description
=> "Target node.",
3488 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3492 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3497 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3502 enum
=> ['secure', 'insecure'],
3503 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3506 migration_network
=> {
3507 type
=> 'string', format
=> 'CIDR',
3508 description
=> "CIDR of the (sub) network that is used for migration.",
3511 "with-local-disks" => {
3513 description
=> "Enable live storage migration for local disk",
3516 targetstorage
=> get_standard_option
('pve-targetstorage', {
3517 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3520 description
=> "Override I/O bandwidth limit (in KiB/s).",
3524 default => 'migrate limit from datacenter or storage config',
3530 description
=> "the task ID.",
3535 my $rpcenv = PVE
::RPCEnvironment
::get
();
3536 my $authuser = $rpcenv->get_user();
3538 my $target = extract_param
($param, 'target');
3540 my $localnode = PVE
::INotify
::nodename
();
3541 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3543 PVE
::Cluster
::check_cfs_quorum
();
3545 PVE
::Cluster
::check_node_exists
($target);
3547 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3549 my $vmid = extract_param
($param, 'vmid');
3551 raise_param_exc
({ force
=> "Only root may use this option." })
3552 if $param->{force
} && $authuser ne 'root@pam';
3554 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3555 if $param->{migration_type
} && $authuser ne 'root@pam';
3557 # allow root only until better network permissions are available
3558 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3559 if $param->{migration_network
} && $authuser ne 'root@pam';
3562 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3564 # try to detect errors early
3566 PVE
::QemuConfig-
>check_lock($conf);
3568 if (PVE
::QemuServer
::check_running
($vmid)) {
3569 die "can't migrate running VM without --online\n" if !$param->{online
};
3571 my $repl_conf = PVE
::ReplicationConfig-
>new();
3572 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3573 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3574 if ($is_replicated && !$is_replicated_to_target) {
3575 if ($param->{force
}) {
3576 warn "WARNING: Node '$target' is not a replication target. Existing replication " .
3577 "jobs will fail after migration!\n";
3579 die "Cannot live-migrate replicated VM to node '$target' - not a replication target." .
3580 " Use 'force' to override.\n";
3584 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3585 $param->{online
} = 0;
3588 my $storecfg = PVE
::Storage
::config
();
3590 if (my $targetstorage = $param->{targetstorage
}) {
3591 my $check_storage = sub {
3592 my ($target_sid) = @_;
3593 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3594 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3595 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3596 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3597 if !$scfg->{content
}->{images
};
3600 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3601 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3604 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3605 if !defined($storagemap->{identity
});
3607 foreach my $source (values %{$storagemap->{entries
}}) {
3608 $check_storage->($source);
3611 $check_storage->($storagemap->{default})
3612 if $storagemap->{default};
3614 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3615 if $storagemap->{identity
};
3617 $param->{storagemap
} = $storagemap;
3619 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3622 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3627 print "Requesting HA migration for VM $vmid to node $target\n";
3629 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3630 PVE
::Tools
::run_command
($cmd);
3634 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3639 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3643 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3646 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3651 __PACKAGE__-
>register_method({
3653 path
=> '{vmid}/monitor',
3657 description
=> "Execute Qemu monitor commands.",
3659 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3660 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3663 additionalProperties
=> 0,
3665 node
=> get_standard_option
('pve-node'),
3666 vmid
=> get_standard_option
('pve-vmid'),
3669 description
=> "The monitor command.",
3673 returns
=> { type
=> 'string'},
3677 my $rpcenv = PVE
::RPCEnvironment
::get
();
3678 my $authuser = $rpcenv->get_user();
3681 my $command = shift;
3682 return $command =~ m/^\s*info(\s+|$)/
3683 || $command =~ m/^\s*help\s*$/;
3686 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3687 if !&$is_ro($param->{command
});
3689 my $vmid = $param->{vmid
};
3691 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3695 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3697 $res = "ERROR: $@" if $@;
3702 __PACKAGE__-
>register_method({
3703 name
=> 'resize_vm',
3704 path
=> '{vmid}/resize',
3708 description
=> "Extend volume size.",
3710 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3713 additionalProperties
=> 0,
3715 node
=> get_standard_option
('pve-node'),
3716 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3717 skiplock
=> get_standard_option
('skiplock'),
3720 description
=> "The disk you want to resize.",
3721 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3725 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3726 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.",
3730 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3736 returns
=> { type
=> 'null'},
3740 my $rpcenv = PVE
::RPCEnvironment
::get
();
3742 my $authuser = $rpcenv->get_user();
3744 my $node = extract_param
($param, 'node');
3746 my $vmid = extract_param
($param, 'vmid');
3748 my $digest = extract_param
($param, 'digest');
3750 my $disk = extract_param
($param, 'disk');
3752 my $sizestr = extract_param
($param, 'size');
3754 my $skiplock = extract_param
($param, 'skiplock');
3755 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3756 if $skiplock && $authuser ne 'root@pam';
3758 my $storecfg = PVE
::Storage
::config
();
3760 my $updatefn = sub {
3762 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3764 die "checksum missmatch (file change by other user?)\n"
3765 if $digest && $digest ne $conf->{digest
};
3766 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3768 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3770 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3772 my (undef, undef, undef, undef, undef, undef, $format) =
3773 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3775 die "can't resize volume: $disk if snapshot exists\n"
3776 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3778 my $volid = $drive->{file
};
3780 die "disk '$disk' has no associated volume\n" if !$volid;
3782 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3784 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3786 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3788 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3789 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3791 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3793 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3794 my ($ext, $newsize, $unit) = ($1, $2, $4);
3797 $newsize = $newsize * 1024;
3798 } elsif ($unit eq 'M') {
3799 $newsize = $newsize * 1024 * 1024;
3800 } elsif ($unit eq 'G') {
3801 $newsize = $newsize * 1024 * 1024 * 1024;
3802 } elsif ($unit eq 'T') {
3803 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3806 $newsize += $size if $ext;
3807 $newsize = int($newsize);
3809 die "shrinking disks is not supported\n" if $newsize < $size;
3811 return if $size == $newsize;
3813 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3815 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3817 $drive->{size
} = $newsize;
3818 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3820 PVE
::QemuConfig-
>write_config($vmid, $conf);
3823 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3827 __PACKAGE__-
>register_method({
3828 name
=> 'snapshot_list',
3829 path
=> '{vmid}/snapshot',
3831 description
=> "List all snapshots.",
3833 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3836 protected
=> 1, # qemu pid files are only readable by root
3838 additionalProperties
=> 0,
3840 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3841 node
=> get_standard_option
('pve-node'),
3850 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3854 description
=> "Snapshot includes RAM.",
3859 description
=> "Snapshot description.",
3863 description
=> "Snapshot creation time",
3865 renderer
=> 'timestamp',
3869 description
=> "Parent snapshot identifier.",
3875 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3880 my $vmid = $param->{vmid
};
3882 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3883 my $snaphash = $conf->{snapshots
} || {};
3887 foreach my $name (keys %$snaphash) {
3888 my $d = $snaphash->{$name};
3891 snaptime
=> $d->{snaptime
} || 0,
3892 vmstate
=> $d->{vmstate
} ?
1 : 0,
3893 description
=> $d->{description
} || '',
3895 $item->{parent
} = $d->{parent
} if $d->{parent
};
3896 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3900 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3903 digest
=> $conf->{digest
},
3904 running
=> $running,
3905 description
=> "You are here!",
3907 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3909 push @$res, $current;
3914 __PACKAGE__-
>register_method({
3916 path
=> '{vmid}/snapshot',
3920 description
=> "Snapshot a VM.",
3922 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3925 additionalProperties
=> 0,
3927 node
=> get_standard_option
('pve-node'),
3928 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3929 snapname
=> get_standard_option
('pve-snapshot-name'),
3933 description
=> "Save the vmstate",
3938 description
=> "A textual description or comment.",
3944 description
=> "the task ID.",
3949 my $rpcenv = PVE
::RPCEnvironment
::get
();
3951 my $authuser = $rpcenv->get_user();
3953 my $node = extract_param
($param, 'node');
3955 my $vmid = extract_param
($param, 'vmid');
3957 my $snapname = extract_param
($param, 'snapname');
3959 die "unable to use snapshot name 'current' (reserved name)\n"
3960 if $snapname eq 'current';
3962 die "unable to use snapshot name 'pending' (reserved name)\n"
3963 if lc($snapname) eq 'pending';
3966 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3967 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3968 $param->{description
});
3971 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3974 __PACKAGE__-
>register_method({
3975 name
=> 'snapshot_cmd_idx',
3976 path
=> '{vmid}/snapshot/{snapname}',
3983 additionalProperties
=> 0,
3985 vmid
=> get_standard_option
('pve-vmid'),
3986 node
=> get_standard_option
('pve-node'),
3987 snapname
=> get_standard_option
('pve-snapshot-name'),
3996 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
4003 push @$res, { cmd
=> 'rollback' };
4004 push @$res, { cmd
=> 'config' };
4009 __PACKAGE__-
>register_method({
4010 name
=> 'update_snapshot_config',
4011 path
=> '{vmid}/snapshot/{snapname}/config',
4015 description
=> "Update snapshot metadata.",
4017 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4020 additionalProperties
=> 0,
4022 node
=> get_standard_option
('pve-node'),
4023 vmid
=> get_standard_option
('pve-vmid'),
4024 snapname
=> get_standard_option
('pve-snapshot-name'),
4028 description
=> "A textual description or comment.",
4032 returns
=> { type
=> 'null' },
4036 my $rpcenv = PVE
::RPCEnvironment
::get
();
4038 my $authuser = $rpcenv->get_user();
4040 my $vmid = extract_param
($param, 'vmid');
4042 my $snapname = extract_param
($param, 'snapname');
4044 return undef if !defined($param->{description
});
4046 my $updatefn = sub {
4048 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4050 PVE
::QemuConfig-
>check_lock($conf);
4052 my $snap = $conf->{snapshots
}->{$snapname};
4054 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4056 $snap->{description
} = $param->{description
} if defined($param->{description
});
4058 PVE
::QemuConfig-
>write_config($vmid, $conf);
4061 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4066 __PACKAGE__-
>register_method({
4067 name
=> 'get_snapshot_config',
4068 path
=> '{vmid}/snapshot/{snapname}/config',
4071 description
=> "Get snapshot configuration",
4073 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4076 additionalProperties
=> 0,
4078 node
=> get_standard_option
('pve-node'),
4079 vmid
=> get_standard_option
('pve-vmid'),
4080 snapname
=> get_standard_option
('pve-snapshot-name'),
4083 returns
=> { type
=> "object" },
4087 my $rpcenv = PVE
::RPCEnvironment
::get
();
4089 my $authuser = $rpcenv->get_user();
4091 my $vmid = extract_param
($param, 'vmid');
4093 my $snapname = extract_param
($param, 'snapname');
4095 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4097 my $snap = $conf->{snapshots
}->{$snapname};
4099 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4104 __PACKAGE__-
>register_method({
4106 path
=> '{vmid}/snapshot/{snapname}/rollback',
4110 description
=> "Rollback VM state to specified snapshot.",
4112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4115 additionalProperties
=> 0,
4117 node
=> get_standard_option
('pve-node'),
4118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4119 snapname
=> get_standard_option
('pve-snapshot-name'),
4124 description
=> "the task ID.",
4129 my $rpcenv = PVE
::RPCEnvironment
::get
();
4131 my $authuser = $rpcenv->get_user();
4133 my $node = extract_param
($param, 'node');
4135 my $vmid = extract_param
($param, 'vmid');
4137 my $snapname = extract_param
($param, 'snapname');
4140 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4141 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4145 # hold migration lock, this makes sure that nobody create replication snapshots
4146 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4149 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4152 __PACKAGE__-
>register_method({
4153 name
=> 'delsnapshot',
4154 path
=> '{vmid}/snapshot/{snapname}',
4158 description
=> "Delete a VM snapshot.",
4160 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4163 additionalProperties
=> 0,
4165 node
=> get_standard_option
('pve-node'),
4166 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4167 snapname
=> get_standard_option
('pve-snapshot-name'),
4171 description
=> "For removal from config file, even if removing disk snapshots fails.",
4177 description
=> "the task ID.",
4182 my $rpcenv = PVE
::RPCEnvironment
::get
();
4184 my $authuser = $rpcenv->get_user();
4186 my $node = extract_param
($param, 'node');
4188 my $vmid = extract_param
($param, 'vmid');
4190 my $snapname = extract_param
($param, 'snapname');
4193 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4194 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4197 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4200 __PACKAGE__-
>register_method({
4202 path
=> '{vmid}/template',
4206 description
=> "Create a Template.",
4208 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4209 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4212 additionalProperties
=> 0,
4214 node
=> get_standard_option
('pve-node'),
4215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4219 description
=> "If you want to convert only 1 disk to base image.",
4220 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4225 returns
=> { type
=> 'null'},
4229 my $rpcenv = PVE
::RPCEnvironment
::get
();
4231 my $authuser = $rpcenv->get_user();
4233 my $node = extract_param
($param, 'node');
4235 my $vmid = extract_param
($param, 'vmid');
4237 my $disk = extract_param
($param, 'disk');
4239 my $updatefn = sub {
4241 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4243 PVE
::QemuConfig-
>check_lock($conf);
4245 die "unable to create template, because VM contains snapshots\n"
4246 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4248 die "you can't convert a template to a template\n"
4249 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4251 die "you can't convert a VM to template if VM is running\n"
4252 if PVE
::QemuServer
::check_running
($vmid);
4255 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4258 $conf->{template
} = 1;
4259 PVE
::QemuConfig-
>write_config($vmid, $conf);
4261 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4264 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4268 __PACKAGE__-
>register_method({
4269 name
=> 'cloudinit_generated_config_dump',
4270 path
=> '{vmid}/cloudinit/dump',
4273 description
=> "Get automatically generated cloudinit config.",
4275 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4278 additionalProperties
=> 0,
4280 node
=> get_standard_option
('pve-node'),
4281 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4283 description
=> 'Config type.',
4285 enum
=> ['user', 'network', 'meta'],
4295 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4297 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});