1 package PVE
::API2
::Qemu
;
10 use Crypt
::OpenSSL
::Random
;
12 use PVE
::Cluster qw
(cfs_read_file cfs_write_file
);;
15 use PVE
::Tools
qw(extract_param);
16 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
18 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::ReplicationConfig
;
21 use PVE
::GuestHelpers
;
24 use PVE
::QemuServer
::Drive
;
25 use PVE
::QemuServer
::CPUConfig
;
26 use PVE
::QemuServer
::Monitor
qw(mon_cmd);
28 use PVE
::RPCEnvironment
;
29 use PVE
::AccessControl
;
33 use PVE
::API2
::Firewall
::VM
;
34 use PVE
::API2
::Qemu
::Agent
;
35 use PVE
::VZDump
::Plugin
;
36 use PVE
::DataCenterConfig
;
40 if (!$ENV{PVE_GENERATING_DOCS
}) {
41 require PVE
::HA
::Env
::PVE2
;
42 import PVE
::HA
::Env
::PVE2
;
43 require PVE
::HA
::Config
;
44 import PVE
::HA
::Config
;
48 use Data
::Dumper
; # fixme: remove
50 use base
qw(PVE::RESTHandler);
52 my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal.";
54 my $resolve_cdrom_alias = sub {
57 if (my $value = $param->{cdrom
}) {
58 $value .= ",media=cdrom" if $value !~ m/media=/;
59 $param->{ide2
} = $value;
60 delete $param->{cdrom
};
64 my $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
65 my $check_storage_access = sub {
66 my ($rpcenv, $authuser, $storecfg, $vmid, $settings, $default_storage) = @_;
68 PVE
::QemuConfig-
>foreach_volume($settings, sub {
69 my ($ds, $drive) = @_;
71 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
73 my $volid = $drive->{file
};
74 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
76 if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit' || (defined($volname) && $volname eq 'cloudinit'))) {
78 } elsif ($isCDROM && ($volid eq 'cdrom')) {
79 $rpcenv->check($authuser, "/", ['Sys.Console']);
80 } elsif (!$isCDROM && ($volid =~ $NEW_DISK_RE)) {
81 my ($storeid, $size) = ($2 || $default_storage, $3);
82 die "no storage ID specified (and no default storage)\n" if !$storeid;
83 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
84 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
85 raise_param_exc
({ storage
=> "storage '$storeid' does not support vm images"})
86 if !$scfg->{content
}->{images
};
88 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
92 $rpcenv->check($authuser, "/storage/$settings->{vmstatestorage}", ['Datastore.AllocateSpace'])
93 if defined($settings->{vmstatestorage
});
96 my $check_storage_access_clone = sub {
97 my ($rpcenv, $authuser, $storecfg, $conf, $storage) = @_;
101 PVE
::QemuConfig-
>foreach_volume($conf, sub {
102 my ($ds, $drive) = @_;
104 my $isCDROM = PVE
::QemuServer
::drive_is_cdrom
($drive);
106 my $volid = $drive->{file
};
108 return if !$volid || $volid eq 'none';
111 if ($volid eq 'cdrom') {
112 $rpcenv->check($authuser, "/", ['Sys.Console']);
114 # we simply allow access
115 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
116 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
117 $sharedvm = 0 if !$scfg->{shared
};
121 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
122 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
123 $sharedvm = 0 if !$scfg->{shared
};
125 $sid = $storage if $storage;
126 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
130 $rpcenv->check($authuser, "/storage/$conf->{vmstatestorage}", ['Datastore.AllocateSpace'])
131 if defined($conf->{vmstatestorage
});
136 # Note: $pool is only needed when creating a VM, because pool permissions
137 # are automatically inherited if VM already exists inside a pool.
138 my $create_disks = sub {
139 my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
146 my ($ds, $disk) = @_;
148 my $volid = $disk->{file
};
149 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
151 if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
152 delete $disk->{size
};
153 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
154 } elsif (defined($volname) && $volname eq 'cloudinit') {
155 $storeid = $storeid // $default_storage;
156 die "no storage ID specified (and no default storage)\n" if !$storeid;
157 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
158 my $name = "vm-$vmid-cloudinit";
162 $fmt = $disk->{format
} // "qcow2";
165 $fmt = $disk->{format
} // "raw";
168 # Initial disk created with 4 MB and aligned to 4MB on regeneration
169 my $ci_size = PVE
::QemuServer
::Cloudinit
::CLOUDINIT_DISK_SIZE
;
170 my $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, $name, $ci_size/1024);
171 $disk->{file
} = $volid;
172 $disk->{media
} = 'cdrom';
173 push @$vollist, $volid;
174 delete $disk->{format
}; # no longer needed
175 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
176 } elsif ($volid =~ $NEW_DISK_RE) {
177 my ($storeid, $size) = ($2 || $default_storage, $3);
178 die "no storage ID specified (and no default storage)\n" if !$storeid;
179 my $defformat = PVE
::Storage
::storage_default_format
($storecfg, $storeid);
180 my $fmt = $disk->{format
} || $defformat;
182 $size = PVE
::Tools
::convert_size
($size, 'gb' => 'kb'); # vdisk_alloc uses kb
185 if ($ds eq 'efidisk0') {
186 ($volid, $size) = PVE
::QemuServer
::create_efidisk
($storecfg, $storeid, $vmid, $fmt, $arch);
188 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storeid, $vmid, $fmt, undef, $size);
190 push @$vollist, $volid;
191 $disk->{file
} = $volid;
192 $disk->{size
} = PVE
::Tools
::convert_size
($size, 'kb' => 'b');
193 delete $disk->{format
}; # no longer needed
194 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
197 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $volid);
199 my $volid_is_new = 1;
202 my $olddrive = PVE
::QemuServer
::parse_drive
($ds, $conf->{$ds});
203 $volid_is_new = undef if $olddrive->{file
} && $olddrive->{file
} eq $volid;
208 PVE
::Storage
::activate_volumes
($storecfg, [ $volid ]) if $storeid;
210 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid);
212 die "volume $volid does not exist\n" if !$size;
214 $disk->{size
} = $size;
217 $res->{$ds} = PVE
::QemuServer
::print_drive
($disk);
221 eval { PVE
::QemuConfig-
>foreach_volume($settings, $code); };
223 # free allocated images on error
225 syslog
('err', "VM $vmid creating disks failed");
226 foreach my $volid (@$vollist) {
227 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
233 # modify vm config if everything went well
234 foreach my $ds (keys %$res) {
235 $conf->{$ds} = $res->{$ds};
241 my $check_cpu_model_access = sub {
242 my ($rpcenv, $authuser, $new, $existing) = @_;
244 return if !defined($new->{cpu
});
246 my $cpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $new->{cpu
});
247 return if !$cpu || !$cpu->{cputype
}; # always allow default
248 my $cputype = $cpu->{cputype
};
250 if ($existing && $existing->{cpu
}) {
251 # changing only other settings doesn't require permissions for CPU model
252 my $existingCpu = PVE
::JSONSchema
::check_format
('pve-vm-cpu-conf', $existing->{cpu
});
253 return if $existingCpu->{cputype
} eq $cputype;
256 if (PVE
::QemuServer
::CPUConfig
::is_custom_model
($cputype)) {
257 $rpcenv->check($authuser, "/nodes", ['Sys.Audit']);
272 my $memoryoptions = {
278 my $hwtypeoptions = {
291 my $generaloptions = {
298 'migrate_downtime' => 1,
299 'migrate_speed' => 1,
312 my $vmpoweroptions = {
319 'vmstatestorage' => 1,
322 my $cloudinitoptions = {
332 my $check_vm_modify_config_perm = sub {
333 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
335 return 1 if $authuser eq 'root@pam';
337 foreach my $opt (@$key_list) {
338 # some checks (e.g., disk, serial port, usb) need to be done somewhere
339 # else, as there the permission can be value dependend
340 next if PVE
::QemuServer
::is_valid_drivename
($opt);
341 next if $opt eq 'cdrom';
342 next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
345 if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
346 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
347 } elsif ($memoryoptions->{$opt}) {
348 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
349 } elsif ($hwtypeoptions->{$opt}) {
350 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
351 } elsif ($generaloptions->{$opt}) {
352 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
353 # special case for startup since it changes host behaviour
354 if ($opt eq 'startup') {
355 $rpcenv->check_full($authuser, "/", ['Sys.Modify']);
357 } elsif ($vmpoweroptions->{$opt}) {
358 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
359 } elsif ($diskoptions->{$opt}) {
360 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
361 } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
362 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
363 } elsif ($cloudinitoptions->{$opt}) {
364 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Cloudinit', 'VM.Config.Network'], 1);
365 } elsif ($opt eq 'vmstate') {
366 # the user needs Disk and PowerMgmt privileges to change the vmstate
367 # also needs privileges on the storage, that will be checked later
368 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
370 # catches hostpci\d+, args, lock, etc.
371 # new options will be checked here
372 die "only root can set '$opt' config\n";
379 __PACKAGE__-
>register_method({
383 description
=> "Virtual machine index (per node).",
385 description
=> "Only list VMs where you have VM.Audit permissons on /vms/<vmid>.",
389 protected
=> 1, # qemu pid files are only readable by root
391 additionalProperties
=> 0,
393 node
=> get_standard_option
('pve-node'),
397 description
=> "Determine the full status of active VMs.",
405 properties
=> $PVE::QemuServer
::vmstatus_return_properties
,
407 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
412 my $rpcenv = PVE
::RPCEnvironment
::get
();
413 my $authuser = $rpcenv->get_user();
415 my $vmstatus = PVE
::QemuServer
::vmstatus
(undef, $param->{full
});
418 foreach my $vmid (keys %$vmstatus) {
419 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
421 my $data = $vmstatus->{$vmid};
428 my $parse_restore_archive = sub {
429 my ($storecfg, $archive) = @_;
431 my ($archive_storeid, $archive_volname) = PVE
::Storage
::parse_volume_id
($archive, 1);
433 if (defined($archive_storeid)) {
434 my $scfg = PVE
::Storage
::storage_config
($storecfg, $archive_storeid);
435 if ($scfg->{type
} eq 'pbs') {
442 my $path = PVE
::Storage
::abs_filesystem_path
($storecfg, $archive);
450 __PACKAGE__-
>register_method({
454 description
=> "Create or restore a virtual machine.",
456 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
457 "For restore (option 'archive'), it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
458 "If you create disks you need 'Datastore.AllocateSpace' on any used storage.",
459 user
=> 'all', # check inside
464 additionalProperties
=> 0,
465 properties
=> PVE
::QemuServer
::json_config_properties
(
467 node
=> get_standard_option
('pve-node'),
468 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
470 description
=> "The backup archive. Either the file system path to a .tar or .vma file (use '-' to pipe data from stdin) or a proxmox storage backup volume identifier.",
474 completion
=> \
&PVE
::QemuServer
::complete_backup_archives
,
476 storage
=> get_standard_option
('pve-storage-id', {
477 description
=> "Default storage.",
479 completion
=> \
&PVE
::QemuServer
::complete_storage
,
484 description
=> "Allow to overwrite existing VM.",
485 requires
=> 'archive',
490 description
=> "Assign a unique random ethernet address.",
491 requires
=> 'archive',
495 type
=> 'string', format
=> 'pve-poolid',
496 description
=> "Add the VM to the specified pool.",
499 description
=> "Override I/O bandwidth limit (in KiB/s).",
503 default => 'restore limit from datacenter or storage config',
509 description
=> "Start VM after it was created successfully.",
519 my $rpcenv = PVE
::RPCEnvironment
::get
();
520 my $authuser = $rpcenv->get_user();
522 my $node = extract_param
($param, 'node');
523 my $vmid = extract_param
($param, 'vmid');
525 my $archive = extract_param
($param, 'archive');
526 my $is_restore = !!$archive;
528 my $bwlimit = extract_param
($param, 'bwlimit');
529 my $force = extract_param
($param, 'force');
530 my $pool = extract_param
($param, 'pool');
531 my $start_after_create = extract_param
($param, 'start');
532 my $storage = extract_param
($param, 'storage');
533 my $unique = extract_param
($param, 'unique');
535 if (defined(my $ssh_keys = $param->{sshkeys
})) {
536 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
537 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
540 PVE
::Cluster
::check_cfs_quorum
();
542 my $filename = PVE
::QemuConfig-
>config_file($vmid);
543 my $storecfg = PVE
::Storage
::config
();
545 if (defined($pool)) {
546 $rpcenv->check_pool_exist($pool);
549 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace'])
550 if defined($storage);
552 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
554 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
556 } elsif ($archive && $force && (-f
$filename) &&
557 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
558 # OK: user has VM.Backup permissions, and want to restore an existing VM
564 &$resolve_cdrom_alias($param);
566 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage);
568 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
570 &$check_cpu_model_access($rpcenv, $authuser, $param);
572 foreach my $opt (keys %$param) {
573 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
574 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
575 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
577 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
578 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
582 PVE
::QemuServer
::add_random_macs
($param);
584 my $keystr = join(' ', keys %$param);
585 raise_param_exc
({ archive
=> "option conflicts with other options ($keystr)"}) if $keystr;
587 if ($archive eq '-') {
588 die "pipe requires cli environment\n"
589 if $rpcenv->{type
} ne 'cli';
590 $archive = { type
=> 'pipe' };
592 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storecfg, $vmid, $archive);
594 $archive = $parse_restore_archive->($storecfg, $archive);
598 my $emsg = $is_restore ?
"unable to restore VM $vmid -" : "unable to create VM $vmid -";
600 eval { PVE
::QemuConfig-
>create_and_lock_config($vmid, $force) };
601 die "$emsg $@" if $@;
603 my $restorefn = sub {
604 my $conf = PVE
::QemuConfig-
>load_config($vmid);
606 PVE
::QemuConfig-
>check_protection($conf, $emsg);
608 die "$emsg vm is running\n" if PVE
::QemuServer
::check_running
($vmid);
611 my $restore_options = {
617 if ($archive->{type
} eq 'file' || $archive->{type
} eq 'pipe') {
618 PVE
::QemuServer
::restore_file_archive
($archive->{path
} // '-', $vmid, $authuser, $restore_options);
619 } elsif ($archive->{type
} eq 'pbs') {
620 PVE
::QemuServer
::restore_proxmox_backup_archive
($archive->{volid
}, $vmid, $authuser, $restore_options);
622 die "unknown backup archive type\n";
624 my $restored_conf = PVE
::QemuConfig-
>load_config($vmid);
625 # Convert restored VM to template if backup was VM template
626 if (PVE
::QemuConfig-
>is_template($restored_conf)) {
627 warn "Convert to template.\n";
628 eval { PVE
::QemuServer
::template_create
($vmid, $restored_conf) };
632 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
635 # ensure no old replication state are exists
636 PVE
::ReplicationState
::delete_guest_states
($vmid);
638 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
640 if ($start_after_create) {
641 print "Execute autostart\n";
642 eval { PVE
::API2
::Qemu-
>vm_start({ vmid
=> $vmid, node
=> $node }) };
648 # ensure no old replication state are exists
649 PVE
::ReplicationState
::delete_guest_states
($vmid);
653 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
657 $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
659 if (!$conf->{boot
}) {
660 my $devs = PVE
::QemuServer
::get_default_bootdevices
($conf);
661 $conf->{boot
} = PVE
::QemuServer
::print_bootorder
($devs);
664 # auto generate uuid if user did not specify smbios1 option
665 if (!$conf->{smbios1
}) {
666 $conf->{smbios1
} = PVE
::QemuServer
::generate_smbios1_uuid
();
669 if ((!defined($conf->{vmgenid
}) || $conf->{vmgenid
} eq '1') && $arch ne 'aarch64') {
670 $conf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
673 PVE
::QemuConfig-
>write_config($vmid, $conf);
679 foreach my $volid (@$vollist) {
680 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
686 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
689 PVE
::QemuConfig-
>lock_config_full($vmid, 1, $realcmd);
691 if ($start_after_create) {
692 print "Execute autostart\n";
693 eval { PVE
::API2
::Qemu-
>vm_start({vmid
=> $vmid, node
=> $node}) };
698 my ($code, $worker_name);
700 $worker_name = 'qmrestore';
702 eval { $restorefn->() };
704 eval { PVE
::QemuConfig-
>remove_lock($vmid, 'create') };
710 $worker_name = 'qmcreate';
712 eval { $createfn->() };
715 my $conffile = PVE
::QemuConfig-
>config_file($vmid);
716 unlink($conffile) or die "failed to remove config file: $!\n";
724 return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
727 __PACKAGE__-
>register_method({
732 description
=> "Directory index",
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
748 subdir
=> { type
=> 'string' },
751 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
757 { subdir
=> 'config' },
758 { subdir
=> 'pending' },
759 { subdir
=> 'status' },
760 { subdir
=> 'unlink' },
761 { subdir
=> 'vncproxy' },
762 { subdir
=> 'termproxy' },
763 { subdir
=> 'migrate' },
764 { subdir
=> 'resize' },
765 { subdir
=> 'move' },
767 { subdir
=> 'rrddata' },
768 { subdir
=> 'monitor' },
769 { subdir
=> 'agent' },
770 { subdir
=> 'snapshot' },
771 { subdir
=> 'spiceproxy' },
772 { subdir
=> 'sendkey' },
773 { subdir
=> 'firewall' },
779 __PACKAGE__-
>register_method ({
780 subclass
=> "PVE::API2::Firewall::VM",
781 path
=> '{vmid}/firewall',
784 __PACKAGE__-
>register_method ({
785 subclass
=> "PVE::API2::Qemu::Agent",
786 path
=> '{vmid}/agent',
789 __PACKAGE__-
>register_method({
791 path
=> '{vmid}/rrd',
793 protected
=> 1, # fixme: can we avoid that?
795 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
797 description
=> "Read VM RRD statistics (returns PNG)",
799 additionalProperties
=> 0,
801 node
=> get_standard_option
('pve-node'),
802 vmid
=> get_standard_option
('pve-vmid'),
804 description
=> "Specify the time frame you are interested in.",
806 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
809 description
=> "The list of datasources you want to display.",
810 type
=> 'string', format
=> 'pve-configid-list',
813 description
=> "The RRD consolidation function",
815 enum
=> [ 'AVERAGE', 'MAX' ],
823 filename
=> { type
=> 'string' },
829 return PVE
::RRD
::create_rrd_graph
(
830 "pve2-vm/$param->{vmid}", $param->{timeframe
},
831 $param->{ds
}, $param->{cf
});
835 __PACKAGE__-
>register_method({
837 path
=> '{vmid}/rrddata',
839 protected
=> 1, # fixme: can we avoid that?
841 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
843 description
=> "Read VM RRD statistics",
845 additionalProperties
=> 0,
847 node
=> get_standard_option
('pve-node'),
848 vmid
=> get_standard_option
('pve-vmid'),
850 description
=> "Specify the time frame you are interested in.",
852 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
855 description
=> "The RRD consolidation function",
857 enum
=> [ 'AVERAGE', 'MAX' ],
872 return PVE
::RRD
::create_rrd_data
(
873 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
877 __PACKAGE__-
>register_method({
879 path
=> '{vmid}/config',
882 description
=> "Get the virtual machine configuration with pending configuration " .
883 "changes applied. Set the 'current' parameter to get the current configuration instead.",
885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
888 additionalProperties
=> 0,
890 node
=> get_standard_option
('pve-node'),
891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
893 description
=> "Get current values (instead of pending values).",
898 snapshot
=> get_standard_option
('pve-snapshot-name', {
899 description
=> "Fetch config values from given snapshot.",
902 my ($cmd, $pname, $cur, $args) = @_;
903 PVE
::QemuConfig-
>snapshot_list($args->[0]);
909 description
=> "The VM configuration.",
911 properties
=> PVE
::QemuServer
::json_config_properties
({
914 description
=> 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
921 raise_param_exc
({ snapshot
=> "cannot use 'snapshot' parameter with 'current'",
922 current
=> "cannot use 'snapshot' parameter with 'current'"})
923 if ($param->{snapshot
} && $param->{current
});
926 if ($param->{snapshot
}) {
927 $conf = PVE
::QemuConfig-
>load_snapshot_config($param->{vmid
}, $param->{snapshot
});
929 $conf = PVE
::QemuConfig-
>load_current_config($param->{vmid
}, $param->{current
});
931 $conf->{cipassword
} = '**********' if $conf->{cipassword
};
936 __PACKAGE__-
>register_method({
937 name
=> 'vm_pending',
938 path
=> '{vmid}/pending',
941 description
=> "Get the virtual machine configuration with both current and pending values.",
943 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
946 additionalProperties
=> 0,
948 node
=> get_standard_option
('pve-node'),
949 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
958 description
=> "Configuration option name.",
962 description
=> "Current value.",
967 description
=> "Pending value.",
972 description
=> "Indicates a pending delete request if present and not 0. " .
973 "The value 2 indicates a force-delete request.",
985 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
987 my $pending_delete_hash = PVE
::QemuConfig-
>parse_pending_delete($conf->{pending
}->{delete});
989 $conf->{cipassword
} = '**********' if defined($conf->{cipassword
});
990 $conf->{pending
}->{cipassword
} = '********** ' if defined($conf->{pending
}->{cipassword
});
992 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
995 # POST/PUT {vmid}/config implementation
997 # The original API used PUT (idempotent) an we assumed that all operations
998 # are fast. But it turned out that almost any configuration change can
999 # involve hot-plug actions, or disk alloc/free. Such actions can take long
1000 # time to complete and have side effects (not idempotent).
1002 # The new implementation uses POST and forks a worker process. We added
1003 # a new option 'background_delay'. If specified we wait up to
1004 # 'background_delay' second for the worker task to complete. It returns null
1005 # if the task is finished within that time, else we return the UPID.
1007 my $update_vm_api = sub {
1008 my ($param, $sync) = @_;
1010 my $rpcenv = PVE
::RPCEnvironment
::get
();
1012 my $authuser = $rpcenv->get_user();
1014 my $node = extract_param
($param, 'node');
1016 my $vmid = extract_param
($param, 'vmid');
1018 my $digest = extract_param
($param, 'digest');
1020 my $background_delay = extract_param
($param, 'background_delay');
1022 if (defined(my $cipassword = $param->{cipassword
})) {
1023 # Same logic as in cloud-init (but with the regex fixed...)
1024 $param->{cipassword
} = PVE
::Tools
::encrypt_pw
($cipassword)
1025 if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
1028 my @paramarr = (); # used for log message
1029 foreach my $key (sort keys %$param) {
1030 my $value = $key eq 'cipassword' ?
'<hidden>' : $param->{$key};
1031 push @paramarr, "-$key", $value;
1034 my $skiplock = extract_param
($param, 'skiplock');
1035 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1036 if $skiplock && $authuser ne 'root@pam';
1038 my $delete_str = extract_param
($param, 'delete');
1040 my $revert_str = extract_param
($param, 'revert');
1042 my $force = extract_param
($param, 'force');
1044 if (defined(my $ssh_keys = $param->{sshkeys
})) {
1045 $ssh_keys = URI
::Escape
::uri_unescape
($ssh_keys);
1046 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys);
1049 die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
1051 my $storecfg = PVE
::Storage
::config
();
1053 my $defaults = PVE
::QemuServer
::load_defaults
();
1055 &$resolve_cdrom_alias($param);
1057 # now try to verify all parameters
1060 foreach my $opt (PVE
::Tools
::split_list
($revert_str)) {
1061 if (!PVE
::QemuServer
::option_exists
($opt)) {
1062 raise_param_exc
({ revert
=> "unknown option '$opt'" });
1065 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1066 "-revert $opt' at the same time" })
1067 if defined($param->{$opt});
1069 $revert->{$opt} = 1;
1073 foreach my $opt (PVE
::Tools
::split_list
($delete_str)) {
1074 $opt = 'ide2' if $opt eq 'cdrom';
1076 raise_param_exc
({ delete => "you can't use '-$opt' and " .
1077 "-delete $opt' at the same time" })
1078 if defined($param->{$opt});
1080 raise_param_exc
({ revert
=> "you can't use '-delete $opt' and " .
1081 "-revert $opt' at the same time" })
1084 if (!PVE
::QemuServer
::option_exists
($opt)) {
1085 raise_param_exc
({ delete => "unknown option '$opt'" });
1091 my $repl_conf = PVE
::ReplicationConfig-
>new();
1092 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
1093 my $check_replication = sub {
1095 return if !$is_replicated;
1096 my $volid = $drive->{file
};
1097 return if !$volid || !($drive->{replicate
}//1);
1098 return if PVE
::QemuServer
::drive_is_cdrom
($drive);
1100 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
1101 die "cannot add non-managed/pass-through volume to a replicated VM\n"
1102 if !defined($storeid);
1104 return if defined($volname) && $volname eq 'cloudinit';
1107 if ($volid =~ $NEW_DISK_RE) {
1109 $format = $drive->{format
} || PVE
::Storage
::storage_default_format
($storecfg, $storeid);
1111 $format = (PVE
::Storage
::parse_volname
($storecfg, $volid))[6];
1113 return if PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
1114 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
1115 return if $scfg->{shared
};
1116 die "cannot add non-replicatable volume to a replicated VM\n";
1119 foreach my $opt (keys %$param) {
1120 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1121 # cleanup drive path
1122 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1123 raise_param_exc
({ $opt => "unable to parse drive options" }) if !$drive;
1124 PVE
::QemuServer
::cleanup_drive_path
($opt, $storecfg, $drive);
1125 $check_replication->($drive);
1126 $param->{$opt} = PVE
::QemuServer
::print_drive
($drive);
1127 } elsif ($opt =~ m/^net(\d+)$/) {
1129 my $net = PVE
::QemuServer
::parse_net
($param->{$opt});
1130 $param->{$opt} = PVE
::QemuServer
::print_net
($net);
1131 } elsif ($opt eq 'vmgenid') {
1132 if ($param->{$opt} eq '1') {
1133 $param->{$opt} = PVE
::QemuServer
::generate_uuid
();
1135 } elsif ($opt eq 'hookscript') {
1136 eval { PVE
::GuestHelpers
::check_hookscript
($param->{$opt}, $storecfg); };
1137 raise_param_exc
({ $opt => $@ }) if $@;
1141 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]);
1143 &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1145 &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param);
1147 my $updatefn = sub {
1149 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1151 die "checksum missmatch (file change by other user?)\n"
1152 if $digest && $digest ne $conf->{digest
};
1154 &$check_cpu_model_access($rpcenv, $authuser, $param, $conf);
1156 # FIXME: 'suspended' lock should probabyl be a state or "weak" lock?!
1157 if (scalar(@delete) && grep { $_ eq 'vmstate'} @delete) {
1158 if (defined($conf->{lock}) && $conf->{lock} eq 'suspended') {
1159 delete $conf->{lock}; # for check lock check, not written out
1160 push @delete, 'lock'; # this is the real deal to write it out
1162 push @delete, 'runningmachine' if $conf->{runningmachine
};
1163 push @delete, 'runningcpu' if $conf->{runningcpu
};
1166 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
1168 foreach my $opt (keys %$revert) {
1169 if (defined($conf->{$opt})) {
1170 $param->{$opt} = $conf->{$opt};
1171 } elsif (defined($conf->{pending
}->{$opt})) {
1176 if ($param->{memory
} || defined($param->{balloon
})) {
1177 my $maxmem = $param->{memory
} || $conf->{pending
}->{memory
} || $conf->{memory
} || $defaults->{memory
};
1178 my $balloon = defined($param->{balloon
}) ?
$param->{balloon
} : $conf->{pending
}->{balloon
} || $conf->{balloon
};
1180 die "balloon value too large (must be smaller than assigned memory)\n"
1181 if $balloon && $balloon > $maxmem;
1184 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: " . join (' ', @paramarr));
1188 print "update VM $vmid: " . join (' ', @paramarr) . "\n";
1190 # write updates to pending section
1192 my $modified = {}; # record what $option we modify
1195 if (my $boot = $conf->{boot
}) {
1196 my $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $boot);
1197 @bootorder = PVE
::Tools
::split_list
($bootcfg->{order
}) if $bootcfg && $bootcfg->{order
};
1199 my $bootorder_deleted = grep {$_ eq 'bootorder'} @delete;
1201 foreach my $opt (@delete) {
1202 $modified->{$opt} = 1;
1203 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1205 # value of what we want to delete, independent if pending or not
1206 my $val = $conf->{$opt} // $conf->{pending
}->{$opt};
1207 if (!defined($val)) {
1208 warn "cannot delete '$opt' - not set in current configuration!\n";
1209 $modified->{$opt} = 0;
1212 my $is_pending_val = defined($conf->{pending
}->{$opt});
1213 delete $conf->{pending
}->{$opt};
1215 # remove from bootorder if necessary
1216 if (!$bootorder_deleted && @bootorder && grep {$_ eq $opt} @bootorder) {
1217 @bootorder = grep {$_ ne $opt} @bootorder;
1218 $conf->{pending
}->{boot
} = PVE
::QemuServer
::print_bootorder
(\
@bootorder);
1219 $modified->{boot
} = 1;
1222 if ($opt =~ m/^unused/) {
1223 my $drive = PVE
::QemuServer
::parse_drive
($opt, $val);
1224 PVE
::QemuConfig-
>check_protection($conf, "can't remove unused disk '$drive->{file}'");
1225 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1226 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, $drive, $rpcenv, $authuser)) {
1227 delete $conf->{$opt};
1228 PVE
::QemuConfig-
>write_config($vmid, $conf);
1230 } elsif ($opt eq 'vmstate') {
1231 PVE
::QemuConfig-
>check_protection($conf, "can't remove vmstate '$val'");
1232 if (PVE
::QemuServer
::try_deallocate_drive
($storecfg, $vmid, $conf, $opt, { file
=> $val }, $rpcenv, $authuser, 1)) {
1233 delete $conf->{$opt};
1234 PVE
::QemuConfig-
>write_config($vmid, $conf);
1236 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1237 PVE
::QemuConfig-
>check_protection($conf, "can't remove drive '$opt'");
1238 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1239 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $val))
1241 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1242 PVE
::QemuConfig-
>write_config($vmid, $conf);
1243 } elsif ($opt =~ m/^serial\d+$/) {
1244 if ($val eq 'socket') {
1245 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1246 } elsif ($authuser ne 'root@pam') {
1247 die "only root can delete '$opt' config for real devices\n";
1249 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1250 PVE
::QemuConfig-
>write_config($vmid, $conf);
1251 } elsif ($opt =~ m/^usb\d+$/) {
1252 if ($val =~ m/spice/) {
1253 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1254 } elsif ($authuser ne 'root@pam') {
1255 die "only root can delete '$opt' config for real devices\n";
1257 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1258 PVE
::QemuConfig-
>write_config($vmid, $conf);
1260 PVE
::QemuConfig-
>add_to_pending_delete($conf, $opt, $force);
1261 PVE
::QemuConfig-
>write_config($vmid, $conf);
1265 foreach my $opt (keys %$param) { # add/change
1266 $modified->{$opt} = 1;
1267 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1268 next if defined($conf->{pending
}->{$opt}) && ($param->{$opt} eq $conf->{pending
}->{$opt}); # skip if nothing changed
1270 my $arch = PVE
::QemuServer
::get_vm_arch
($conf);
1272 if (PVE
::QemuServer
::is_valid_drivename
($opt)) {
1273 my $drive = PVE
::QemuServer
::parse_drive
($opt, $param->{$opt});
1274 # FIXME: cloudinit: CDROM or Disk?
1275 if (PVE
::QemuServer
::drive_is_cdrom
($drive)) { # CDROM
1276 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
1278 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
1280 PVE
::QemuServer
::vmconfig_register_unused_drive
($storecfg, $vmid, $conf, PVE
::QemuServer
::parse_drive
($opt, $conf->{pending
}->{$opt}))
1281 if defined($conf->{pending
}->{$opt});
1283 &$create_disks($rpcenv, $authuser, $conf->{pending
}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
1284 } elsif ($opt =~ m/^serial\d+/) {
1285 if ((!defined($conf->{$opt}) || $conf->{$opt} eq 'socket') && $param->{$opt} eq 'socket') {
1286 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1287 } elsif ($authuser ne 'root@pam') {
1288 die "only root can modify '$opt' config for real devices\n";
1290 $conf->{pending
}->{$opt} = $param->{$opt};
1291 } elsif ($opt =~ m/^usb\d+/) {
1292 if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
1293 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
1294 } elsif ($authuser ne 'root@pam') {
1295 die "only root can modify '$opt' config for real devices\n";
1297 $conf->{pending
}->{$opt} = $param->{$opt};
1299 $conf->{pending
}->{$opt} = $param->{$opt};
1301 if ($opt eq 'boot') {
1302 my $new_bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $param->{$opt});
1303 if ($new_bootcfg->{order
}) {
1304 my @devs = PVE
::Tools
::split_list
($new_bootcfg->{order
});
1305 for my $dev (@devs) {
1306 my $exists = $conf->{$dev} || $conf->{pending
}->{$dev};
1307 my $deleted = grep {$_ eq $dev} @delete;
1308 die "invalid bootorder: device '$dev' does not exist'\n"
1309 if !$exists || $deleted;
1312 # remove legacy boot order settings if new one set
1313 $conf->{pending
}->{$opt} = PVE
::QemuServer
::print_bootorder
(\
@devs);
1314 PVE
::QemuConfig-
>add_to_pending_delete($conf, "bootdisk")
1315 if $conf->{bootdisk
};
1319 PVE
::QemuConfig-
>remove_from_pending_delete($conf, $opt);
1320 PVE
::QemuConfig-
>write_config($vmid, $conf);
1323 # remove pending changes when nothing changed
1324 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1325 my $changes = PVE
::QemuConfig-
>cleanup_pending($conf);
1326 PVE
::QemuConfig-
>write_config($vmid, $conf) if $changes;
1328 return if !scalar(keys %{$conf->{pending
}});
1330 my $running = PVE
::QemuServer
::check_running
($vmid);
1332 # apply pending changes
1334 $conf = PVE
::QemuConfig-
>load_config($vmid); # update/reload
1338 PVE
::QemuServer
::vmconfig_hotplug_pending
($vmid, $conf, $storecfg, $modified, $errors);
1340 PVE
::QemuServer
::vmconfig_apply_pending
($vmid, $conf, $storecfg, $running, $errors);
1342 raise_param_exc
($errors) if scalar(keys %$errors);
1351 my $upid = $rpcenv->fork_worker('qmconfig', $vmid, $authuser, $worker);
1353 if ($background_delay) {
1355 # Note: It would be better to do that in the Event based HTTPServer
1356 # to avoid blocking call to sleep.
1358 my $end_time = time() + $background_delay;
1360 my $task = PVE
::Tools
::upid_decode
($upid);
1363 while (time() < $end_time) {
1364 $running = PVE
::ProcFSTools
::check_process_running
($task->{pid
}, $task->{pstart
});
1366 sleep(1); # this gets interrupted when child process ends
1370 my $status = PVE
::Tools
::upid_read_status
($upid);
1371 return if $status eq 'OK';
1380 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
1383 my $vm_config_perm_list = [
1388 'VM.Config.Network',
1390 'VM.Config.Options',
1391 'VM.Config.Cloudinit',
1394 __PACKAGE__-
>register_method({
1395 name
=> 'update_vm_async',
1396 path
=> '{vmid}/config',
1400 description
=> "Set virtual machine options (asynchrounous API).",
1402 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1405 additionalProperties
=> 0,
1406 properties
=> PVE
::QemuServer
::json_config_properties
(
1408 node
=> get_standard_option
('pve-node'),
1409 vmid
=> get_standard_option
('pve-vmid'),
1410 skiplock
=> get_standard_option
('skiplock'),
1412 type
=> 'string', format
=> 'pve-configid-list',
1413 description
=> "A list of settings you want to delete.",
1417 type
=> 'string', format
=> 'pve-configid-list',
1418 description
=> "Revert a pending change.",
1423 description
=> $opt_force_description,
1425 requires
=> 'delete',
1429 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1433 background_delay
=> {
1435 description
=> "Time to wait for the task to finish. We return 'null' if the task finish within that time.",
1446 code
=> $update_vm_api,
1449 __PACKAGE__-
>register_method({
1450 name
=> 'update_vm',
1451 path
=> '{vmid}/config',
1455 description
=> "Set virtual machine options (synchrounous API) - You should consider using the POST method instead for any actions involving hotplug or storage allocation.",
1457 check
=> ['perm', '/vms/{vmid}', $vm_config_perm_list, any
=> 1],
1460 additionalProperties
=> 0,
1461 properties
=> PVE
::QemuServer
::json_config_properties
(
1463 node
=> get_standard_option
('pve-node'),
1464 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1465 skiplock
=> get_standard_option
('skiplock'),
1467 type
=> 'string', format
=> 'pve-configid-list',
1468 description
=> "A list of settings you want to delete.",
1472 type
=> 'string', format
=> 'pve-configid-list',
1473 description
=> "Revert a pending change.",
1478 description
=> $opt_force_description,
1480 requires
=> 'delete',
1484 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1490 returns
=> { type
=> 'null' },
1493 &$update_vm_api($param, 1);
1498 __PACKAGE__-
>register_method({
1499 name
=> 'destroy_vm',
1504 description
=> "Destroy the vm (also delete all used/owned volumes).",
1506 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1512 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
1513 skiplock
=> get_standard_option
('skiplock'),
1516 description
=> "Remove vmid from backup cron jobs.",
1527 my $rpcenv = PVE
::RPCEnvironment
::get
();
1528 my $authuser = $rpcenv->get_user();
1529 my $vmid = $param->{vmid
};
1531 my $skiplock = $param->{skiplock
};
1532 raise_param_exc
({ skiplock
=> "Only root may use this option." })
1533 if $skiplock && $authuser ne 'root@pam';
1535 my $early_checks = sub {
1537 my $conf = PVE
::QemuConfig-
>load_config($vmid);
1538 PVE
::QemuConfig-
>check_protection($conf, "can't remove VM $vmid");
1540 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("vm:$vmid");
1542 if (!$param->{purge
}) {
1543 die "unable to remove VM $vmid - used in HA resources and purge parameter not set.\n"
1545 # don't allow destroy if with replication jobs but no purge param
1546 my $repl_conf = PVE
::ReplicationConfig-
>new();
1547 $repl_conf->check_for_existing_jobs($vmid);
1550 die "VM $vmid is running - destroy failed\n"
1551 if PVE
::QemuServer
::check_running
($vmid);
1561 my $storecfg = PVE
::Storage
::config
();
1563 syslog
('info', "destroy VM $vmid: $upid\n");
1564 PVE
::QemuConfig-
>lock_config($vmid, sub {
1565 # repeat, config might have changed
1566 my $ha_managed = $early_checks->();
1568 PVE
::QemuServer
::destroy_vm
($storecfg, $vmid, $skiplock, { lock => 'destroyed' });
1570 PVE
::AccessControl
::remove_vm_access
($vmid);
1571 PVE
::Firewall
::remove_vmfw_conf
($vmid);
1572 if ($param->{purge
}) {
1573 print "purging VM $vmid from related configurations..\n";
1574 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
1575 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
1578 PVE
::HA
::Config
::delete_service_from_config
("vm:$vmid");
1579 print "NOTE: removed VM $vmid from HA resource configuration.\n";
1583 # only now remove the zombie config, else we can have reuse race
1584 PVE
::QemuConfig-
>destroy_config($vmid);
1588 return $rpcenv->fork_worker('qmdestroy', $vmid, $authuser, $realcmd);
1591 __PACKAGE__-
>register_method({
1593 path
=> '{vmid}/unlink',
1597 description
=> "Unlink/delete disk images.",
1599 check
=> [ 'perm', '/vms/{vmid}', ['VM.Config.Disk']],
1602 additionalProperties
=> 0,
1604 node
=> get_standard_option
('pve-node'),
1605 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
1607 type
=> 'string', format
=> 'pve-configid-list',
1608 description
=> "A list of disk IDs you want to delete.",
1612 description
=> $opt_force_description,
1617 returns
=> { type
=> 'null'},
1621 $param->{delete} = extract_param
($param, 'idlist');
1623 __PACKAGE__-
>update_vm($param);
1628 # uses good entropy, each char is limited to 6 bit to get printable chars simply
1629 my $gen_rand_chars = sub {
1632 die "invalid length $length" if $length < 1;
1634 my $min = ord('!'); # first printable ascii
1636 my $rand_bytes = Crypt
::OpenSSL
::Random
::random_bytes
($length);
1637 die "failed to generate random bytes!\n"
1640 my $str = join('', map { chr((ord($_) & 0x3F) + $min) } split('', $rand_bytes));
1647 __PACKAGE__-
>register_method({
1649 path
=> '{vmid}/vncproxy',
1653 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1655 description
=> "Creates a TCP VNC proxy connections.",
1657 additionalProperties
=> 0,
1659 node
=> get_standard_option
('pve-node'),
1660 vmid
=> get_standard_option
('pve-vmid'),
1664 description
=> "starts websockify instead of vncproxy",
1666 'generate-password' => {
1670 description
=> "Generates a random password to be used as ticket instead of the API ticket.",
1675 additionalProperties
=> 0,
1677 user
=> { type
=> 'string' },
1678 ticket
=> { type
=> 'string' },
1681 description
=> "Returned if requested with 'generate-password' param."
1682 ." Consists of printable ASCII characters ('!' .. '~').",
1685 cert
=> { type
=> 'string' },
1686 port
=> { type
=> 'integer' },
1687 upid
=> { type
=> 'string' },
1693 my $rpcenv = PVE
::RPCEnvironment
::get
();
1695 my $authuser = $rpcenv->get_user();
1697 my $vmid = $param->{vmid
};
1698 my $node = $param->{node
};
1699 my $websocket = $param->{websocket
};
1701 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1705 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1706 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1709 my $authpath = "/vms/$vmid";
1711 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1712 my $password = $ticket;
1713 if ($param->{'generate-password'}) {
1714 $password = $gen_rand_chars->(8);
1717 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1723 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1724 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1725 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1726 # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
1727 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, defined($serial) ?
'-t' : '-T');
1729 $family = PVE
::Tools
::get_host_address_family
($node);
1732 my $port = PVE
::Tools
::next_vnc_port
($family);
1739 syslog
('info', "starting vnc proxy $upid\n");
1743 if (defined($serial)) {
1745 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $serial, '-escape', '0' ];
1747 $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
1748 '-timeout', $timeout, '-authpath', $authpath,
1749 '-perm', 'Sys.Console'];
1751 if ($param->{websocket
}) {
1752 $ENV{PVE_VNC_TICKET
} = $password; # pass ticket to vncterm
1753 push @$cmd, '-notls', '-listen', 'localhost';
1756 push @$cmd, '-c', @$remcmd, @$termcmd;
1758 PVE
::Tools
::run_command
($cmd);
1762 $ENV{LC_PVE_TICKET
} = $password if $websocket; # set ticket with "qm vncproxy"
1764 $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
1766 my $sock = IO
::Socket
::IP-
>new(
1771 GetAddrInfoFlags
=> 0,
1772 ) or die "failed to create socket: $!\n";
1773 # Inside the worker we shouldn't have any previous alarms
1774 # running anyway...:
1776 local $SIG{ALRM
} = sub { die "connection timed out\n" };
1778 accept(my $cli, $sock) or die "connection failed: $!\n";
1781 if (PVE
::Tools
::run_command
($cmd,
1782 output
=> '>&'.fileno($cli),
1783 input
=> '<&'.fileno($cli),
1786 die "Failed to run vncproxy.\n";
1793 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1795 PVE
::Tools
::wait_for_vnc_port
($port);
1804 $res->{password
} = $password if $param->{'generate-password'};
1809 __PACKAGE__-
>register_method({
1810 name
=> 'termproxy',
1811 path
=> '{vmid}/termproxy',
1815 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1817 description
=> "Creates a TCP proxy connections.",
1819 additionalProperties
=> 0,
1821 node
=> get_standard_option
('pve-node'),
1822 vmid
=> get_standard_option
('pve-vmid'),
1826 enum
=> [qw(serial0 serial1 serial2 serial3)],
1827 description
=> "opens a serial terminal (defaults to display)",
1832 additionalProperties
=> 0,
1834 user
=> { type
=> 'string' },
1835 ticket
=> { type
=> 'string' },
1836 port
=> { type
=> 'integer' },
1837 upid
=> { type
=> 'string' },
1843 my $rpcenv = PVE
::RPCEnvironment
::get
();
1845 my $authuser = $rpcenv->get_user();
1847 my $vmid = $param->{vmid
};
1848 my $node = $param->{node
};
1849 my $serial = $param->{serial
};
1851 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # check if VM exists
1853 if (!defined($serial)) {
1855 my $vga = PVE
::QemuServer
::parse_vga
($conf->{vga
});
1856 $serial = $vga->{type
} if $vga->{type
} =~ m/^serial\d+$/;
1860 my $authpath = "/vms/$vmid";
1862 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
1867 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1868 (undef, $family) = PVE
::Cluster
::remote_node_ip
($node);
1869 my $sshinfo = PVE
::SSHInfo
::get_ssh_info
($node);
1870 $remcmd = PVE
::SSHInfo
::ssh_info_to_command
($sshinfo, '-t');
1871 push @$remcmd, '--';
1873 $family = PVE
::Tools
::get_host_address_family
($node);
1876 my $port = PVE
::Tools
::next_vnc_port
($family);
1878 my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
1879 push @$termcmd, '-iface', $serial if $serial;
1884 syslog
('info', "starting qemu termproxy $upid\n");
1886 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1887 '--perm', 'VM.Console', '--'];
1888 push @$cmd, @$remcmd, @$termcmd;
1890 PVE
::Tools
::run_command
($cmd);
1893 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1895 PVE
::Tools
::wait_for_vnc_port
($port);
1905 __PACKAGE__-
>register_method({
1906 name
=> 'vncwebsocket',
1907 path
=> '{vmid}/vncwebsocket',
1910 description
=> "You also need to pass a valid ticket (vncticket).",
1911 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1913 description
=> "Opens a weksocket for VNC traffic.",
1915 additionalProperties
=> 0,
1917 node
=> get_standard_option
('pve-node'),
1918 vmid
=> get_standard_option
('pve-vmid'),
1920 description
=> "Ticket from previous call to vncproxy.",
1925 description
=> "Port number returned by previous vncproxy call.",
1935 port
=> { type
=> 'string' },
1941 my $rpcenv = PVE
::RPCEnvironment
::get
();
1943 my $authuser = $rpcenv->get_user();
1945 my $vmid = $param->{vmid
};
1946 my $node = $param->{node
};
1948 my $authpath = "/vms/$vmid";
1950 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1952 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node); # VM exists ?
1954 # Note: VNC ports are acessible from outside, so we do not gain any
1955 # security if we verify that $param->{port} belongs to VM $vmid. This
1956 # check is done by verifying the VNC ticket (inside VNC protocol).
1958 my $port = $param->{port
};
1960 return { port
=> $port };
1963 __PACKAGE__-
>register_method({
1964 name
=> 'spiceproxy',
1965 path
=> '{vmid}/spiceproxy',
1970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1972 description
=> "Returns a SPICE configuration to connect to the VM.",
1974 additionalProperties
=> 0,
1976 node
=> get_standard_option
('pve-node'),
1977 vmid
=> get_standard_option
('pve-vmid'),
1978 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1981 returns
=> get_standard_option
('remote-viewer-config'),
1985 my $rpcenv = PVE
::RPCEnvironment
::get
();
1987 my $authuser = $rpcenv->get_user();
1989 my $vmid = $param->{vmid
};
1990 my $node = $param->{node
};
1991 my $proxy = $param->{proxy
};
1993 my $conf = PVE
::QemuConfig-
>load_config($vmid, $node);
1994 my $title = "VM $vmid";
1995 $title .= " - ". $conf->{name
} if $conf->{name
};
1997 my $port = PVE
::QemuServer
::spice_port
($vmid);
1999 my ($ticket, undef, $remote_viewer_config) =
2000 PVE
::AccessControl
::remote_viewer_config
($authuser, $vmid, $node, $proxy, $title, $port);
2002 mon_cmd
($vmid, "set_password", protocol
=> 'spice', password
=> $ticket);
2003 mon_cmd
($vmid, "expire_password", protocol
=> 'spice', time => "+30");
2005 return $remote_viewer_config;
2008 __PACKAGE__-
>register_method({
2010 path
=> '{vmid}/status',
2013 description
=> "Directory index",
2018 additionalProperties
=> 0,
2020 node
=> get_standard_option
('pve-node'),
2021 vmid
=> get_standard_option
('pve-vmid'),
2029 subdir
=> { type
=> 'string' },
2032 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
2038 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2041 { subdir
=> 'current' },
2042 { subdir
=> 'start' },
2043 { subdir
=> 'stop' },
2044 { subdir
=> 'reset' },
2045 { subdir
=> 'shutdown' },
2046 { subdir
=> 'suspend' },
2047 { subdir
=> 'reboot' },
2053 __PACKAGE__-
>register_method({
2054 name
=> 'vm_status',
2055 path
=> '{vmid}/status/current',
2058 protected
=> 1, # qemu pid files are only readable by root
2059 description
=> "Get virtual machine status.",
2061 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2064 additionalProperties
=> 0,
2066 node
=> get_standard_option
('pve-node'),
2067 vmid
=> get_standard_option
('pve-vmid'),
2073 %$PVE::QemuServer
::vmstatus_return_properties
,
2075 description
=> "HA manager service status.",
2079 description
=> "Qemu VGA configuration supports spice.",
2084 description
=> "Qemu GuestAgent enabled in config.",
2094 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
2096 my $vmstatus = PVE
::QemuServer
::vmstatus
($param->{vmid
}, 1);
2097 my $status = $vmstatus->{$param->{vmid
}};
2099 $status->{ha
} = PVE
::HA
::Config
::get_service_status
("vm:$param->{vmid}");
2101 $status->{spice
} = 1 if PVE
::QemuServer
::vga_conf_has_spice
($conf->{vga
});
2102 $status->{agent
} = 1 if PVE
::QemuServer
::get_qga_key
($conf, 'enabled');
2107 __PACKAGE__-
>register_method({
2109 path
=> '{vmid}/status/start',
2113 description
=> "Start virtual machine.",
2115 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2118 additionalProperties
=> 0,
2120 node
=> get_standard_option
('pve-node'),
2121 vmid
=> get_standard_option
('pve-vmid',
2122 { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
2123 skiplock
=> get_standard_option
('skiplock'),
2124 stateuri
=> get_standard_option
('pve-qm-stateuri'),
2125 migratedfrom
=> get_standard_option
('pve-node',{ optional
=> 1 }),
2128 enum
=> ['secure', 'insecure'],
2129 description
=> "Migration traffic is encrypted using an SSH " .
2130 "tunnel by default. On secure, completely private networks " .
2131 "this can be disabled to increase performance.",
2134 migration_network
=> {
2135 type
=> 'string', format
=> 'CIDR',
2136 description
=> "CIDR of the (sub) network that is used for migration.",
2139 machine
=> get_standard_option
('pve-qemu-machine'),
2141 description
=> "Override QEMU's -cpu argument with the given string.",
2145 targetstorage
=> get_standard_option
('pve-targetstorage'),
2147 description
=> "Wait maximal timeout seconds.",
2150 default => 'max(30, vm memory in GiB)',
2161 my $rpcenv = PVE
::RPCEnvironment
::get
();
2162 my $authuser = $rpcenv->get_user();
2164 my $node = extract_param
($param, 'node');
2165 my $vmid = extract_param
($param, 'vmid');
2166 my $timeout = extract_param
($param, 'timeout');
2168 my $machine = extract_param
($param, 'machine');
2169 my $force_cpu = extract_param
($param, 'force-cpu');
2171 my $get_root_param = sub {
2172 my $value = extract_param
($param, $_[0]);
2173 raise_param_exc
({ "$_[0]" => "Only root may use this option." })
2174 if $value && $authuser ne 'root@pam';
2178 my $stateuri = $get_root_param->('stateuri');
2179 my $skiplock = $get_root_param->('skiplock');
2180 my $migratedfrom = $get_root_param->('migratedfrom');
2181 my $migration_type = $get_root_param->('migration_type');
2182 my $migration_network = $get_root_param->('migration_network');
2183 my $targetstorage = $get_root_param->('targetstorage');
2187 if ($targetstorage) {
2188 raise_param_exc
({ targetstorage
=> "targetstorage can only by used with migratedfrom." })
2190 $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
2191 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
2195 # read spice ticket from STDIN
2197 my $nbd_protocol_version = 0;
2198 my $replicated_volumes = {};
2199 if ($stateuri && ($stateuri eq 'tcp' || $stateuri eq 'unix') && $migratedfrom && ($rpcenv->{type
} eq 'cli')) {
2200 while (defined(my $line = <STDIN
>)) {
2202 if ($line =~ m/^spice_ticket: (.+)$/) {
2204 } elsif ($line =~ m/^nbd_protocol_version: (\d+)$/) {
2205 $nbd_protocol_version = $1;
2206 } elsif ($line =~ m/^replicated_volume: (.*)$/) {
2207 $replicated_volumes->{$1} = 1;
2209 # fallback for old source node
2210 $spice_ticket = $line;
2215 PVE
::Cluster
::check_cfs_quorum
();
2217 my $storecfg = PVE
::Storage
::config
();
2219 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && !$stateuri && $rpcenv->{type
} ne 'ha') {
2223 print "Requesting HA start for VM $vmid\n";
2225 my $cmd = ['ha-manager', 'set', "vm:$vmid", '--state', 'started'];
2226 PVE
::Tools
::run_command
($cmd);
2230 return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd);
2237 syslog
('info', "start VM $vmid: $upid\n");
2239 my $migrate_opts = {
2240 migratedfrom
=> $migratedfrom,
2241 spice_ticket
=> $spice_ticket,
2242 network
=> $migration_network,
2243 type
=> $migration_type,
2244 storagemap
=> $storagemap,
2245 nbd_proto_version
=> $nbd_protocol_version,
2246 replicated_volumes
=> $replicated_volumes,
2250 statefile
=> $stateuri,
2251 skiplock
=> $skiplock,
2252 forcemachine
=> $machine,
2253 timeout
=> $timeout,
2254 forcecpu
=> $force_cpu,
2257 PVE
::QemuServer
::vm_start
($storecfg, $vmid, $params, $migrate_opts);
2261 return $rpcenv->fork_worker('qmstart', $vmid, $authuser, $realcmd);
2265 __PACKAGE__-
>register_method({
2267 path
=> '{vmid}/status/stop',
2271 description
=> "Stop virtual machine. The qemu process will exit immediately. This" .
2272 "is akin to pulling the power plug of a running computer and may damage the VM data",
2274 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2277 additionalProperties
=> 0,
2279 node
=> get_standard_option
('pve-node'),
2280 vmid
=> get_standard_option
('pve-vmid',
2281 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2282 skiplock
=> get_standard_option
('skiplock'),
2283 migratedfrom
=> get_standard_option
('pve-node', { optional
=> 1 }),
2285 description
=> "Wait maximal timeout seconds.",
2291 description
=> "Do not deactivate storage volumes.",
2304 my $rpcenv = PVE
::RPCEnvironment
::get
();
2305 my $authuser = $rpcenv->get_user();
2307 my $node = extract_param
($param, 'node');
2308 my $vmid = extract_param
($param, 'vmid');
2310 my $skiplock = extract_param
($param, 'skiplock');
2311 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2312 if $skiplock && $authuser ne 'root@pam';
2314 my $keepActive = extract_param
($param, 'keepActive');
2315 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2316 if $keepActive && $authuser ne 'root@pam';
2318 my $migratedfrom = extract_param
($param, 'migratedfrom');
2319 raise_param_exc
({ migratedfrom
=> "Only root may use this option." })
2320 if $migratedfrom && $authuser ne 'root@pam';
2323 my $storecfg = PVE
::Storage
::config
();
2325 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && ($rpcenv->{type
} ne 'ha') && !defined($migratedfrom)) {
2330 print "Requesting HA stop for VM $vmid\n";
2332 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", '0'];
2333 PVE
::Tools
::run_command
($cmd);
2337 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2343 syslog
('info', "stop VM $vmid: $upid\n");
2345 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0,
2346 $param->{timeout
}, 0, 1, $keepActive, $migratedfrom);
2350 return $rpcenv->fork_worker('qmstop', $vmid, $authuser, $realcmd);
2354 __PACKAGE__-
>register_method({
2356 path
=> '{vmid}/status/reset',
2360 description
=> "Reset virtual machine.",
2362 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2365 additionalProperties
=> 0,
2367 node
=> get_standard_option
('pve-node'),
2368 vmid
=> get_standard_option
('pve-vmid',
2369 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2370 skiplock
=> get_standard_option
('skiplock'),
2379 my $rpcenv = PVE
::RPCEnvironment
::get
();
2381 my $authuser = $rpcenv->get_user();
2383 my $node = extract_param
($param, 'node');
2385 my $vmid = extract_param
($param, 'vmid');
2387 my $skiplock = extract_param
($param, 'skiplock');
2388 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2389 if $skiplock && $authuser ne 'root@pam';
2391 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2396 PVE
::QemuServer
::vm_reset
($vmid, $skiplock);
2401 return $rpcenv->fork_worker('qmreset', $vmid, $authuser, $realcmd);
2404 my sub vm_is_paused
{
2406 my $qmpstatus = eval {
2407 PVE
::QemuConfig
::assert_config_exists_on_node
($vmid);
2408 mon_cmd
($vmid, "query-status");
2411 return $qmpstatus && $qmpstatus->{status
} eq "paused";
2414 __PACKAGE__-
>register_method({
2415 name
=> 'vm_shutdown',
2416 path
=> '{vmid}/status/shutdown',
2420 description
=> "Shutdown virtual machine. This is similar to pressing the power button on a physical machine." .
2421 "This will send an ACPI event for the guest OS, which should then proceed to a clean shutdown.",
2423 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2426 additionalProperties
=> 0,
2428 node
=> get_standard_option
('pve-node'),
2429 vmid
=> get_standard_option
('pve-vmid',
2430 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2431 skiplock
=> get_standard_option
('skiplock'),
2433 description
=> "Wait maximal timeout seconds.",
2439 description
=> "Make sure the VM stops.",
2445 description
=> "Do not deactivate storage volumes.",
2458 my $rpcenv = PVE
::RPCEnvironment
::get
();
2459 my $authuser = $rpcenv->get_user();
2461 my $node = extract_param
($param, 'node');
2462 my $vmid = extract_param
($param, 'vmid');
2464 my $skiplock = extract_param
($param, 'skiplock');
2465 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2466 if $skiplock && $authuser ne 'root@pam';
2468 my $keepActive = extract_param
($param, 'keepActive');
2469 raise_param_exc
({ keepActive
=> "Only root may use this option." })
2470 if $keepActive && $authuser ne 'root@pam';
2472 my $storecfg = PVE
::Storage
::config
();
2476 # if vm is paused, do not shutdown (but stop if forceStop = 1)
2477 # otherwise, we will infer a shutdown command, but run into the timeout,
2478 # then when the vm is resumed, it will instantly shutdown
2480 # checking the qmp status here to get feedback to the gui/cli/api
2481 # and the status query should not take too long
2482 if (vm_is_paused
($vmid)) {
2483 if ($param->{forceStop
}) {
2484 warn "VM is paused - stop instead of shutdown\n";
2487 die "VM is paused - cannot shutdown\n";
2491 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
2493 my $timeout = $param->{timeout
} // 60;
2497 print "Requesting HA stop for VM $vmid\n";
2499 my $cmd = ['ha-manager', 'crm-command', 'stop', "vm:$vmid", "$timeout"];
2500 PVE
::Tools
::run_command
($cmd);
2504 return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd);
2511 syslog
('info', "shutdown VM $vmid: $upid\n");
2513 PVE
::QemuServer
::vm_stop
($storecfg, $vmid, $skiplock, 0, $param->{timeout
},
2514 $shutdown, $param->{forceStop
}, $keepActive);
2518 return $rpcenv->fork_worker('qmshutdown', $vmid, $authuser, $realcmd);
2522 __PACKAGE__-
>register_method({
2523 name
=> 'vm_reboot',
2524 path
=> '{vmid}/status/reboot',
2528 description
=> "Reboot the VM by shutting it down, and starting it again. Applies pending changes.",
2530 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2533 additionalProperties
=> 0,
2535 node
=> get_standard_option
('pve-node'),
2536 vmid
=> get_standard_option
('pve-vmid',
2537 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2539 description
=> "Wait maximal timeout seconds for the shutdown.",
2552 my $rpcenv = PVE
::RPCEnvironment
::get
();
2553 my $authuser = $rpcenv->get_user();
2555 my $node = extract_param
($param, 'node');
2556 my $vmid = extract_param
($param, 'vmid');
2558 die "VM is paused - cannot shutdown\n" if vm_is_paused
($vmid);
2560 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2565 syslog
('info', "requesting reboot of VM $vmid: $upid\n");
2566 PVE
::QemuServer
::vm_reboot
($vmid, $param->{timeout
});
2570 return $rpcenv->fork_worker('qmreboot', $vmid, $authuser, $realcmd);
2573 __PACKAGE__-
>register_method({
2574 name
=> 'vm_suspend',
2575 path
=> '{vmid}/status/suspend',
2579 description
=> "Suspend virtual machine.",
2581 description
=> "You need 'VM.PowerMgmt' on /vms/{vmid}, and if you have set 'todisk',".
2582 " you need also 'VM.Config.Disk' on /vms/{vmid} and 'Datastore.AllocateSpace'".
2583 " on the storage for the vmstate.",
2584 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2587 additionalProperties
=> 0,
2589 node
=> get_standard_option
('pve-node'),
2590 vmid
=> get_standard_option
('pve-vmid',
2591 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2592 skiplock
=> get_standard_option
('skiplock'),
2597 description
=> 'If set, suspends the VM to disk. Will be resumed on next VM start.',
2599 statestorage
=> get_standard_option
('pve-storage-id', {
2600 description
=> "The storage for the VM state",
2601 requires
=> 'todisk',
2603 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2613 my $rpcenv = PVE
::RPCEnvironment
::get
();
2614 my $authuser = $rpcenv->get_user();
2616 my $node = extract_param
($param, 'node');
2617 my $vmid = extract_param
($param, 'vmid');
2619 my $todisk = extract_param
($param, 'todisk') // 0;
2621 my $statestorage = extract_param
($param, 'statestorage');
2623 my $skiplock = extract_param
($param, 'skiplock');
2624 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2625 if $skiplock && $authuser ne 'root@pam';
2627 die "VM $vmid not running\n" if !PVE
::QemuServer
::check_running
($vmid);
2629 die "Cannot suspend HA managed VM to disk\n"
2630 if $todisk && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
2632 # early check for storage permission, for better user feedback
2634 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
2636 if (!$statestorage) {
2637 # get statestorage from config if none is given
2638 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2639 my $storecfg = PVE
::Storage
::config
();
2640 $statestorage = PVE
::QemuServer
::find_vmstate_storage
($conf, $storecfg);
2643 $rpcenv->check($authuser, "/storage/$statestorage", ['Datastore.AllocateSpace']);
2649 syslog
('info', "suspend VM $vmid: $upid\n");
2651 PVE
::QemuServer
::vm_suspend
($vmid, $skiplock, $todisk, $statestorage);
2656 my $taskname = $todisk ?
'qmsuspend' : 'qmpause';
2657 return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
2660 __PACKAGE__-
>register_method({
2661 name
=> 'vm_resume',
2662 path
=> '{vmid}/status/resume',
2666 description
=> "Resume virtual machine.",
2668 check
=> ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
2671 additionalProperties
=> 0,
2673 node
=> get_standard_option
('pve-node'),
2674 vmid
=> get_standard_option
('pve-vmid',
2675 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2676 skiplock
=> get_standard_option
('skiplock'),
2677 nocheck
=> { type
=> 'boolean', optional
=> 1 },
2687 my $rpcenv = PVE
::RPCEnvironment
::get
();
2689 my $authuser = $rpcenv->get_user();
2691 my $node = extract_param
($param, 'node');
2693 my $vmid = extract_param
($param, 'vmid');
2695 my $skiplock = extract_param
($param, 'skiplock');
2696 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2697 if $skiplock && $authuser ne 'root@pam';
2699 my $nocheck = extract_param
($param, 'nocheck');
2700 raise_param_exc
({ nocheck
=> "Only root may use this option." })
2701 if $nocheck && $authuser ne 'root@pam';
2703 my $to_disk_suspended;
2705 PVE
::QemuConfig-
>lock_config($vmid, sub {
2706 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2707 $to_disk_suspended = PVE
::QemuConfig-
>has_lock($conf, 'suspended');
2711 die "VM $vmid not running\n"
2712 if !$to_disk_suspended && !PVE
::QemuServer
::check_running
($vmid, $nocheck);
2717 syslog
('info', "resume VM $vmid: $upid\n");
2719 if (!$to_disk_suspended) {
2720 PVE
::QemuServer
::vm_resume
($vmid, $skiplock, $nocheck);
2722 my $storecfg = PVE
::Storage
::config
();
2723 PVE
::QemuServer
::vm_start
($storecfg, $vmid, { skiplock
=> $skiplock });
2729 return $rpcenv->fork_worker('qmresume', $vmid, $authuser, $realcmd);
2732 __PACKAGE__-
>register_method({
2733 name
=> 'vm_sendkey',
2734 path
=> '{vmid}/sendkey',
2738 description
=> "Send key event to virtual machine.",
2740 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
2743 additionalProperties
=> 0,
2745 node
=> get_standard_option
('pve-node'),
2746 vmid
=> get_standard_option
('pve-vmid',
2747 { completion
=> \
&PVE
::QemuServer
::complete_vmid_running
}),
2748 skiplock
=> get_standard_option
('skiplock'),
2750 description
=> "The key (qemu monitor encoding).",
2755 returns
=> { type
=> 'null'},
2759 my $rpcenv = PVE
::RPCEnvironment
::get
();
2761 my $authuser = $rpcenv->get_user();
2763 my $node = extract_param
($param, 'node');
2765 my $vmid = extract_param
($param, 'vmid');
2767 my $skiplock = extract_param
($param, 'skiplock');
2768 raise_param_exc
({ skiplock
=> "Only root may use this option." })
2769 if $skiplock && $authuser ne 'root@pam';
2771 PVE
::QemuServer
::vm_sendkey
($vmid, $skiplock, $param->{key
});
2776 __PACKAGE__-
>register_method({
2777 name
=> 'vm_feature',
2778 path
=> '{vmid}/feature',
2782 description
=> "Check if feature for virtual machine is available.",
2784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2787 additionalProperties
=> 0,
2789 node
=> get_standard_option
('pve-node'),
2790 vmid
=> get_standard_option
('pve-vmid'),
2792 description
=> "Feature to check.",
2794 enum
=> [ 'snapshot', 'clone', 'copy' ],
2796 snapname
=> get_standard_option
('pve-snapshot-name', {
2804 hasFeature
=> { type
=> 'boolean' },
2807 items
=> { type
=> 'string' },
2814 my $node = extract_param
($param, 'node');
2816 my $vmid = extract_param
($param, 'vmid');
2818 my $snapname = extract_param
($param, 'snapname');
2820 my $feature = extract_param
($param, 'feature');
2822 my $running = PVE
::QemuServer
::check_running
($vmid);
2824 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2827 my $snap = $conf->{snapshots
}->{$snapname};
2828 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2831 my $storecfg = PVE
::Storage
::config
();
2833 my $nodelist = PVE
::QemuServer
::shared_nodes
($conf, $storecfg);
2834 my $hasFeature = PVE
::QemuConfig-
>has_feature($feature, $conf, $storecfg, $snapname, $running);
2837 hasFeature
=> $hasFeature,
2838 nodes
=> [ keys %$nodelist ],
2842 __PACKAGE__-
>register_method({
2844 path
=> '{vmid}/clone',
2848 description
=> "Create a copy of virtual machine/template.",
2850 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, and 'VM.Allocate' permissions " .
2851 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
2852 "'Datastore.AllocateSpace' on any used storage.",
2855 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
2857 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
2858 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
2863 additionalProperties
=> 0,
2865 node
=> get_standard_option
('pve-node'),
2866 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
2867 newid
=> get_standard_option
('pve-vmid', {
2868 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
2869 description
=> 'VMID for the clone.' }),
2872 type
=> 'string', format
=> 'dns-name',
2873 description
=> "Set a name for the new VM.",
2878 description
=> "Description for the new VM.",
2882 type
=> 'string', format
=> 'pve-poolid',
2883 description
=> "Add the new VM to the specified pool.",
2885 snapname
=> get_standard_option
('pve-snapshot-name', {
2888 storage
=> get_standard_option
('pve-storage-id', {
2889 description
=> "Target storage for full clone.",
2893 description
=> "Target format for file storage. Only valid for full clone.",
2896 enum
=> [ 'raw', 'qcow2', 'vmdk'],
2901 description
=> "Create a full copy of all disks. This is always done when " .
2902 "you clone a normal VM. For VM templates, we try to create a linked clone by default.",
2904 target
=> get_standard_option
('pve-node', {
2905 description
=> "Target node. Only allowed if the original VM is on shared storage.",
2909 description
=> "Override I/O bandwidth limit (in KiB/s).",
2913 default => 'clone limit from datacenter or storage config',
2923 my $rpcenv = PVE
::RPCEnvironment
::get
();
2924 my $authuser = $rpcenv->get_user();
2926 my $node = extract_param
($param, 'node');
2927 my $vmid = extract_param
($param, 'vmid');
2928 my $newid = extract_param
($param, 'newid');
2929 my $pool = extract_param
($param, 'pool');
2930 $rpcenv->check_pool_exist($pool) if defined($pool);
2932 my $snapname = extract_param
($param, 'snapname');
2933 my $storage = extract_param
($param, 'storage');
2934 my $format = extract_param
($param, 'format');
2935 my $target = extract_param
($param, 'target');
2937 my $localnode = PVE
::INotify
::nodename
();
2939 if ($target && ($target eq $localnode || $target eq 'localhost')) {
2943 PVE
::Cluster
::check_node_exists
($target) if $target;
2945 my $storecfg = PVE
::Storage
::config
();
2948 # check if storage is enabled on local node
2949 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
2951 # check if storage is available on target node
2952 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
2953 # clone only works if target storage is shared
2954 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2955 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
2959 PVE
::Cluster
::check_cfs_quorum
();
2961 my $running = PVE
::QemuServer
::check_running
($vmid) || 0;
2964 # do all tests after lock but before forking worker - if possible
2966 my $conf = PVE
::QemuConfig-
>load_config($vmid);
2967 PVE
::QemuConfig-
>check_lock($conf);
2969 my $verify_running = PVE
::QemuServer
::check_running
($vmid) || 0;
2970 die "unexpected state change\n" if $verify_running != $running;
2972 die "snapshot '$snapname' does not exist\n"
2973 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
2975 my $full = extract_param
($param, 'full') // !PVE
::QemuConfig-
>is_template($conf);
2977 die "parameter 'storage' not allowed for linked clones\n"
2978 if defined($storage) && !$full;
2980 die "parameter 'format' not allowed for linked clones\n"
2981 if defined($format) && !$full;
2983 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
2985 my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
2987 die "can't clone VM to node '$target' (VM uses local storage)\n"
2988 if $target && !$sharedvm;
2990 my $conffile = PVE
::QemuConfig-
>config_file($newid);
2991 die "unable to create VM $newid: config file already exists\n"
2994 my $newconf = { lock => 'clone' };
2999 foreach my $opt (keys %$oldconf) {
3000 my $value = $oldconf->{$opt};
3002 # do not copy snapshot related info
3003 next if $opt eq 'snapshots' || $opt eq 'parent' || $opt eq 'snaptime' ||
3004 $opt eq 'vmstate' || $opt eq 'snapstate';
3006 # no need to copy unused images, because VMID(owner) changes anyways
3007 next if $opt =~ m/^unused\d+$/;
3009 # always change MAC! address
3010 if ($opt =~ m/^net(\d+)$/) {
3011 my $net = PVE
::QemuServer
::parse_net
($value);
3012 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
3013 $net->{macaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
3014 $newconf->{$opt} = PVE
::QemuServer
::print_net
($net);
3015 } elsif (PVE
::QemuServer
::is_valid_drivename
($opt)) {
3016 my $drive = PVE
::QemuServer
::parse_drive
($opt, $value);
3017 die "unable to parse drive options for '$opt'\n" if !$drive;
3018 if (PVE
::QemuServer
::drive_is_cdrom
($drive, 1)) {
3019 $newconf->{$opt} = $value; # simply copy configuration
3021 if ($full || PVE
::QemuServer
::drive_is_cloudinit
($drive)) {
3022 die "Full clone feature is not supported for drive '$opt'\n"
3023 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $drive->{file
}, $snapname, $running);
3024 $fullclone->{$opt} = 1;
3026 # not full means clone instead of copy
3027 die "Linked clone feature is not supported for drive '$opt'\n"
3028 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $drive->{file
}, $snapname, $running);
3030 $drives->{$opt} = $drive;
3031 next if PVE
::QemuServer
::drive_is_cloudinit
($drive);
3032 push @$vollist, $drive->{file
};
3035 # copy everything else
3036 $newconf->{$opt} = $value;
3040 # auto generate a new uuid
3041 my $smbios1 = PVE
::QemuServer
::parse_smbios1
($newconf->{smbios1
} || '');
3042 $smbios1->{uuid
} = PVE
::QemuServer
::generate_uuid
();
3043 $newconf->{smbios1
} = PVE
::QemuServer
::print_smbios1
($smbios1);
3044 # auto generate a new vmgenid only if the option was set for template
3045 if ($newconf->{vmgenid
}) {
3046 $newconf->{vmgenid
} = PVE
::QemuServer
::generate_uuid
();
3049 delete $newconf->{template
};
3051 if ($param->{name
}) {
3052 $newconf->{name
} = $param->{name
};
3054 $newconf->{name
} = "Copy-of-VM-" . ($oldconf->{name
} // $vmid);
3057 if ($param->{description
}) {
3058 $newconf->{description
} = $param->{description
};
3061 # create empty/temp config - this fails if VM already exists on other node
3062 # FIXME use PVE::QemuConfig->create_and_lock_config and adapt code
3063 PVE
::Tools
::file_set_contents
($conffile, "# qmclone temporary file\nlock: clone\n");
3068 my $newvollist = [];
3075 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3077 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
3079 my $bwlimit = extract_param
($param, 'bwlimit');
3081 my $total_jobs = scalar(keys %{$drives});
3084 foreach my $opt (keys %$drives) {
3085 my $drive = $drives->{$opt};
3086 my $skipcomplete = ($total_jobs != $i); # finish after last drive
3087 my $completion = $skipcomplete ?
'skip' : 'complete';
3089 my $src_sid = PVE
::Storage
::parse_volume_id
($drive->{file
});
3090 my $storage_list = [ $src_sid ];
3091 push @$storage_list, $storage if defined($storage);
3092 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', $storage_list, $bwlimit);
3094 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $opt, $drive, $snapname,
3095 $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
3096 $jobs, $completion, $oldconf->{agent
}, $clonelimit, $oldconf);
3098 $newconf->{$opt} = PVE
::QemuServer
::print_drive
($newdrive);
3100 PVE
::QemuConfig-
>write_config($newid, $newconf);
3104 delete $newconf->{lock};
3106 # do not write pending changes
3107 if (my @changes = keys %{$newconf->{pending
}}) {
3108 my $pending = join(',', @changes);
3109 warn "found pending changes for '$pending', discarding for clone\n";
3110 delete $newconf->{pending
};
3113 PVE
::QemuConfig-
>write_config($newid, $newconf);
3116 # always deactivate volumes - avoid lvm LVs to be active on several nodes
3117 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
3118 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
3120 my $newconffile = PVE
::QemuConfig-
>config_file($newid, $target);
3121 die "Failed to move config to node '$target' - rename failed: $!\n"
3122 if !rename($conffile, $newconffile);
3125 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
3128 eval { PVE
::QemuServer
::qemu_blockjobs_cancel
($vmid, $jobs) };
3129 sleep 1; # some storage like rbd need to wait before release volume - really?
3131 foreach my $volid (@$newvollist) {
3132 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
3136 PVE
::Firewall
::remove_vmfw_conf
($newid);
3138 unlink $conffile; # avoid races -> last thing before die
3140 die "clone failed: $err";
3146 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
3148 return $rpcenv->fork_worker('qmclone', $vmid, $authuser, $realcmd);
3151 # Aquire exclusive lock lock for $newid
3152 my $lock_target_vm = sub {
3153 return PVE
::QemuConfig-
>lock_config_full($newid, 1, $clonefn);
3156 # exclusive lock if VM is running - else shared lock is enough;
3158 return PVE
::QemuConfig-
>lock_config_full($vmid, 1, $lock_target_vm);
3160 return PVE
::QemuConfig-
>lock_config_shared($vmid, 1, $lock_target_vm);
3164 __PACKAGE__-
>register_method({
3165 name
=> 'move_vm_disk',
3166 path
=> '{vmid}/move_disk',
3170 description
=> "Move volume to different storage.",
3172 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, and 'Datastore.AllocateSpace' permissions on the storage.",
3174 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3175 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
3179 additionalProperties
=> 0,
3181 node
=> get_standard_option
('pve-node'),
3182 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3185 description
=> "The disk you want to move.",
3186 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3188 storage
=> get_standard_option
('pve-storage-id', {
3189 description
=> "Target storage.",
3190 completion
=> \
&PVE
::QemuServer
::complete_storage
,
3194 description
=> "Target Format.",
3195 enum
=> [ 'raw', 'qcow2', 'vmdk' ],
3200 description
=> "Delete the original disk after successful copy. By default the original disk is kept as unused disk.",
3206 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3211 description
=> "Override I/O bandwidth limit (in KiB/s).",
3215 default => 'move limit from datacenter or storage config',
3221 description
=> "the task ID.",
3226 my $rpcenv = PVE
::RPCEnvironment
::get
();
3227 my $authuser = $rpcenv->get_user();
3229 my $node = extract_param
($param, 'node');
3230 my $vmid = extract_param
($param, 'vmid');
3231 my $digest = extract_param
($param, 'digest');
3232 my $disk = extract_param
($param, 'disk');
3233 my $storeid = extract_param
($param, 'storage');
3234 my $format = extract_param
($param, 'format');
3236 my $storecfg = PVE
::Storage
::config
();
3238 my $updatefn = sub {
3239 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3240 PVE
::QemuConfig-
>check_lock($conf);
3242 die "VM config checksum missmatch (file change by other user?)\n"
3243 if $digest && $digest ne $conf->{digest
};
3245 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3247 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3249 die "disk '$disk' has no associated volume\n" if !$drive->{file
};
3250 die "you can't move a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive, 1);
3252 my $old_volid = $drive->{file
};
3254 my ($oldstoreid, $oldvolname) = PVE
::Storage
::parse_volume_id
($old_volid);
3255 if ($oldvolname =~ m/\.(raw|qcow2|vmdk)$/){
3259 die "you can't move to the same storage with same format\n" if $oldstoreid eq $storeid &&
3260 (!$format || !$oldfmt || $oldfmt eq $format);
3262 # this only checks snapshots because $disk is passed!
3263 my $snapshotted = PVE
::QemuServer
::Drive
::is_volume_in_use
($storecfg, $conf, $disk, $old_volid);
3264 die "you can't move a disk with snapshots and delete the source\n"
3265 if $snapshotted && $param->{delete};
3267 PVE
::Cluster
::log_msg
('info', $authuser, "move disk VM $vmid: move --disk $disk --storage $storeid");
3269 my $running = PVE
::QemuServer
::check_running
($vmid);
3271 PVE
::Storage
::activate_volumes
($storecfg, [ $drive->{file
} ]);
3274 my $newvollist = [];
3280 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
3282 warn "moving disk with snapshots, snapshots will not be moved!\n"
3285 my $bwlimit = extract_param
($param, 'bwlimit');
3286 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$oldstoreid, $storeid], $bwlimit);
3288 my $newdrive = PVE
::QemuServer
::clone_disk
($storecfg, $vmid, $running, $disk, $drive, undef,
3289 $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit, $conf);
3291 $conf->{$disk} = PVE
::QemuServer
::print_drive
($newdrive);
3293 PVE
::QemuConfig-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
3295 # convert moved disk to base if part of template
3296 PVE
::QemuServer
::template_create
($vmid, $conf, $disk)
3297 if PVE
::QemuConfig-
>is_template($conf);
3299 PVE
::QemuConfig-
>write_config($vmid, $conf);
3301 my $do_trim = PVE
::QemuServer
::get_qga_key
($conf, 'fstrim_cloned_disks');
3302 if ($running && $do_trim && PVE
::QemuServer
::qga_check_running
($vmid)) {
3303 eval { mon_cmd
($vmid, "guest-fstrim") };
3307 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
3308 PVE
::Storage
::deactivate_volumes
($storecfg, [ $newdrive->{file
} ])
3314 foreach my $volid (@$newvollist) {
3315 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid) };
3318 die "storage migration failed: $err";
3321 if ($param->{delete}) {
3323 PVE
::Storage
::deactivate_volumes
($storecfg, [$old_volid]);
3324 PVE
::Storage
::vdisk_free
($storecfg, $old_volid);
3330 return $rpcenv->fork_worker('qmmove', $vmid, $authuser, $realcmd);
3333 return PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3336 my $check_vm_disks_local = sub {
3337 my ($storecfg, $vmconf, $vmid) = @_;
3339 my $local_disks = {};
3341 # add some more information to the disks e.g. cdrom
3342 PVE
::QemuServer
::foreach_volid
($vmconf, sub {
3343 my ($volid, $attr) = @_;
3345 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
3347 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid);
3348 return if $scfg->{shared
};
3350 # The shared attr here is just a special case where the vdisk
3351 # is marked as shared manually
3352 return if $attr->{shared
};
3353 return if $attr->{cdrom
} and $volid eq "none";
3355 if (exists $local_disks->{$volid}) {
3356 @{$local_disks->{$volid}}{keys %$attr} = values %$attr
3358 $local_disks->{$volid} = $attr;
3359 # ensure volid is present in case it's needed
3360 $local_disks->{$volid}->{volid
} = $volid;
3364 return $local_disks;
3367 __PACKAGE__-
>register_method({
3368 name
=> 'migrate_vm_precondition',
3369 path
=> '{vmid}/migrate',
3373 description
=> "Get preconditions for migration.",
3375 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3378 additionalProperties
=> 0,
3380 node
=> get_standard_option
('pve-node'),
3381 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3382 target
=> get_standard_option
('pve-node', {
3383 description
=> "Target node.",
3384 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3392 running
=> { type
=> 'boolean' },
3396 description
=> "List nodes allowed for offline migration, only passed if VM is offline"
3398 not_allowed_nodes
=> {
3401 description
=> "List not allowed nodes with additional informations, only passed if VM is offline"
3405 description
=> "List local disks including CD-Rom, unsused and not referenced disks"
3407 local_resources
=> {
3409 description
=> "List local resources e.g. pci, usb"
3416 my $rpcenv = PVE
::RPCEnvironment
::get
();
3418 my $authuser = $rpcenv->get_user();
3420 PVE
::Cluster
::check_cfs_quorum
();
3424 my $vmid = extract_param
($param, 'vmid');
3425 my $target = extract_param
($param, 'target');
3426 my $localnode = PVE
::INotify
::nodename
();
3430 my $vmconf = PVE
::QemuConfig-
>load_config($vmid);
3431 my $storecfg = PVE
::Storage
::config
();
3434 # try to detect errors early
3435 PVE
::QemuConfig-
>check_lock($vmconf);
3437 $res->{running
} = PVE
::QemuServer
::check_running
($vmid) ?
1:0;
3439 # if vm is not running, return target nodes where local storage is available
3440 # for offline migration
3441 if (!$res->{running
}) {
3442 $res->{allowed_nodes
} = [];
3443 my $checked_nodes = PVE
::QemuServer
::check_local_storage_availability
($vmconf, $storecfg);
3444 delete $checked_nodes->{$localnode};
3446 foreach my $node (keys %$checked_nodes) {
3447 if (!defined $checked_nodes->{$node}->{unavailable_storages
}) {
3448 push @{$res->{allowed_nodes
}}, $node;
3452 $res->{not_allowed_nodes
} = $checked_nodes;
3456 my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
3457 $res->{local_disks
} = [ values %$local_disks ];;
3459 my $local_resources = PVE
::QemuServer
::check_local_resources
($vmconf, 1);
3461 $res->{local_resources
} = $local_resources;
3468 __PACKAGE__-
>register_method({
3469 name
=> 'migrate_vm',
3470 path
=> '{vmid}/migrate',
3474 description
=> "Migrate virtual machine. Creates a new migration task.",
3476 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
3479 additionalProperties
=> 0,
3481 node
=> get_standard_option
('pve-node'),
3482 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3483 target
=> get_standard_option
('pve-node', {
3484 description
=> "Target node.",
3485 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
3489 description
=> "Use online/live migration if VM is running. Ignored if VM is stopped.",
3494 description
=> "Allow to migrate VMs which use local devices. Only root may use this option.",
3499 enum
=> ['secure', 'insecure'],
3500 description
=> "Migration traffic is encrypted using an SSH tunnel by default. On secure, completely private networks this can be disabled to increase performance.",
3503 migration_network
=> {
3504 type
=> 'string', format
=> 'CIDR',
3505 description
=> "CIDR of the (sub) network that is used for migration.",
3508 "with-local-disks" => {
3510 description
=> "Enable live storage migration for local disk",
3513 targetstorage
=> get_standard_option
('pve-targetstorage', {
3514 completion
=> \
&PVE
::QemuServer
::complete_migration_storage
,
3517 description
=> "Override I/O bandwidth limit (in KiB/s).",
3521 default => 'migrate limit from datacenter or storage config',
3527 description
=> "the task ID.",
3532 my $rpcenv = PVE
::RPCEnvironment
::get
();
3533 my $authuser = $rpcenv->get_user();
3535 my $target = extract_param
($param, 'target');
3537 my $localnode = PVE
::INotify
::nodename
();
3538 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
3540 PVE
::Cluster
::check_cfs_quorum
();
3542 PVE
::Cluster
::check_node_exists
($target);
3544 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
3546 my $vmid = extract_param
($param, 'vmid');
3548 raise_param_exc
({ force
=> "Only root may use this option." })
3549 if $param->{force
} && $authuser ne 'root@pam';
3551 raise_param_exc
({ migration_type
=> "Only root may use this option." })
3552 if $param->{migration_type
} && $authuser ne 'root@pam';
3554 # allow root only until better network permissions are available
3555 raise_param_exc
({ migration_network
=> "Only root may use this option." })
3556 if $param->{migration_network
} && $authuser ne 'root@pam';
3559 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3561 # try to detect errors early
3563 PVE
::QemuConfig-
>check_lock($conf);
3565 if (PVE
::QemuServer
::check_running
($vmid)) {
3566 die "can't migrate running VM without --online\n" if !$param->{online
};
3568 my $repl_conf = PVE
::ReplicationConfig-
>new();
3569 my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
3570 my $is_replicated_to_target = defined($repl_conf->find_local_replication_job($vmid, $target));
3571 if (!$param->{force
} && $is_replicated && !$is_replicated_to_target) {
3572 die "Cannot live-migrate replicated VM to node '$target' - not a replication " .
3573 "target. Use 'force' to override.\n";
3576 warn "VM isn't running. Doing offline migration instead.\n" if $param->{online
};
3577 $param->{online
} = 0;
3580 my $storecfg = PVE
::Storage
::config
();
3582 if (my $targetstorage = $param->{targetstorage
}) {
3583 my $check_storage = sub {
3584 my ($target_sid) = @_;
3585 PVE
::Storage
::storage_check_node
($storecfg, $target_sid, $target);
3586 $rpcenv->check($authuser, "/storage/$target_sid", ['Datastore.AllocateSpace']);
3587 my $scfg = PVE
::Storage
::storage_config
($storecfg, $target_sid);
3588 raise_param_exc
({ targetstorage
=> "storage '$target_sid' does not support vm images"})
3589 if !$scfg->{content
}->{images
};
3592 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
3593 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
3596 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
3597 if !defined($storagemap->{identity
});
3599 foreach my $source (values %{$storagemap->{entries
}}) {
3600 $check_storage->($source);
3603 $check_storage->($storagemap->{default})
3604 if $storagemap->{default};
3606 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target)
3607 if $storagemap->{identity
};
3609 $param->{storagemap
} = $storagemap;
3611 PVE
::QemuServer
::check_storage_availability
($storecfg, $conf, $target);
3614 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
3619 print "Requesting HA migration for VM $vmid to node $target\n";
3621 my $cmd = ['ha-manager', 'migrate', "vm:$vmid", $target];
3622 PVE
::Tools
::run_command
($cmd);
3626 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
3631 PVE
::QemuMigrate-
>migrate($target, $targetip, $vmid, $param);
3635 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
3638 return $rpcenv->fork_worker('qmigrate', $vmid, $authuser, $worker);
3643 __PACKAGE__-
>register_method({
3645 path
=> '{vmid}/monitor',
3649 description
=> "Execute Qemu monitor commands.",
3651 description
=> "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')",
3652 check
=> ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
3655 additionalProperties
=> 0,
3657 node
=> get_standard_option
('pve-node'),
3658 vmid
=> get_standard_option
('pve-vmid'),
3661 description
=> "The monitor command.",
3665 returns
=> { type
=> 'string'},
3669 my $rpcenv = PVE
::RPCEnvironment
::get
();
3670 my $authuser = $rpcenv->get_user();
3673 my $command = shift;
3674 return $command =~ m/^\s*info(\s+|$)/
3675 || $command =~ m/^\s*help\s*$/;
3678 $rpcenv->check_full($authuser, "/", ['Sys.Modify'])
3679 if !&$is_ro($param->{command
});
3681 my $vmid = $param->{vmid
};
3683 my $conf = PVE
::QemuConfig-
>load_config ($vmid); # check if VM exists
3687 $res = PVE
::QemuServer
::Monitor
::hmp_cmd
($vmid, $param->{command
});
3689 $res = "ERROR: $@" if $@;
3694 __PACKAGE__-
>register_method({
3695 name
=> 'resize_vm',
3696 path
=> '{vmid}/resize',
3700 description
=> "Extend volume size.",
3702 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3705 additionalProperties
=> 0,
3707 node
=> get_standard_option
('pve-node'),
3708 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3709 skiplock
=> get_standard_option
('skiplock'),
3712 description
=> "The disk you want to resize.",
3713 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
3717 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
3718 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.",
3722 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
3728 returns
=> { type
=> 'null'},
3732 my $rpcenv = PVE
::RPCEnvironment
::get
();
3734 my $authuser = $rpcenv->get_user();
3736 my $node = extract_param
($param, 'node');
3738 my $vmid = extract_param
($param, 'vmid');
3740 my $digest = extract_param
($param, 'digest');
3742 my $disk = extract_param
($param, 'disk');
3744 my $sizestr = extract_param
($param, 'size');
3746 my $skiplock = extract_param
($param, 'skiplock');
3747 raise_param_exc
({ skiplock
=> "Only root may use this option." })
3748 if $skiplock && $authuser ne 'root@pam';
3750 my $storecfg = PVE
::Storage
::config
();
3752 my $updatefn = sub {
3754 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3756 die "checksum missmatch (file change by other user?)\n"
3757 if $digest && $digest ne $conf->{digest
};
3758 PVE
::QemuConfig-
>check_lock($conf) if !$skiplock;
3760 die "disk '$disk' does not exist\n" if !$conf->{$disk};
3762 my $drive = PVE
::QemuServer
::parse_drive
($disk, $conf->{$disk});
3764 my (undef, undef, undef, undef, undef, undef, $format) =
3765 PVE
::Storage
::parse_volname
($storecfg, $drive->{file
});
3767 die "can't resize volume: $disk if snapshot exists\n"
3768 if %{$conf->{snapshots
}} && $format eq 'qcow2';
3770 my $volid = $drive->{file
};
3772 die "disk '$disk' has no associated volume\n" if !$volid;
3774 die "you can't resize a cdrom\n" if PVE
::QemuServer
::drive_is_cdrom
($drive);
3776 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
3778 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
3780 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
3781 my $size = PVE
::Storage
::volume_size_info
($storecfg, $volid, 5);
3783 die "Could not determine current size of volume '$volid'\n" if !defined($size);
3785 die "internal error" if $sizestr !~ m/^(\+)?(\d+(\.\d+)?)([KMGT])?$/;
3786 my ($ext, $newsize, $unit) = ($1, $2, $4);
3789 $newsize = $newsize * 1024;
3790 } elsif ($unit eq 'M') {
3791 $newsize = $newsize * 1024 * 1024;
3792 } elsif ($unit eq 'G') {
3793 $newsize = $newsize * 1024 * 1024 * 1024;
3794 } elsif ($unit eq 'T') {
3795 $newsize = $newsize * 1024 * 1024 * 1024 * 1024;
3798 $newsize += $size if $ext;
3799 $newsize = int($newsize);
3801 die "shrinking disks is not supported\n" if $newsize < $size;
3803 return if $size == $newsize;
3805 PVE
::Cluster
::log_msg
('info', $authuser, "update VM $vmid: resize --disk $disk --size $sizestr");
3807 PVE
::QemuServer
::qemu_block_resize
($vmid, "drive-$disk", $storecfg, $volid, $newsize);
3809 $drive->{size
} = $newsize;
3810 $conf->{$disk} = PVE
::QemuServer
::print_drive
($drive);
3812 PVE
::QemuConfig-
>write_config($vmid, $conf);
3815 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
3819 __PACKAGE__-
>register_method({
3820 name
=> 'snapshot_list',
3821 path
=> '{vmid}/snapshot',
3823 description
=> "List all snapshots.",
3825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
3828 protected
=> 1, # qemu pid files are only readable by root
3830 additionalProperties
=> 0,
3832 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3833 node
=> get_standard_option
('pve-node'),
3842 description
=> "Snapshot identifier. Value 'current' identifies the current VM.",
3846 description
=> "Snapshot includes RAM.",
3851 description
=> "Snapshot description.",
3855 description
=> "Snapshot creation time",
3857 renderer
=> 'timestamp',
3861 description
=> "Parent snapshot identifier.",
3867 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
3872 my $vmid = $param->{vmid
};
3874 my $conf = PVE
::QemuConfig-
>load_config($vmid);
3875 my $snaphash = $conf->{snapshots
} || {};
3879 foreach my $name (keys %$snaphash) {
3880 my $d = $snaphash->{$name};
3883 snaptime
=> $d->{snaptime
} || 0,
3884 vmstate
=> $d->{vmstate
} ?
1 : 0,
3885 description
=> $d->{description
} || '',
3887 $item->{parent
} = $d->{parent
} if $d->{parent
};
3888 $item->{snapstate
} = $d->{snapstate
} if $d->{snapstate
};
3892 my $running = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
3895 digest
=> $conf->{digest
},
3896 running
=> $running,
3897 description
=> "You are here!",
3899 $current->{parent
} = $conf->{parent
} if $conf->{parent
};
3901 push @$res, $current;
3906 __PACKAGE__-
>register_method({
3908 path
=> '{vmid}/snapshot',
3912 description
=> "Snapshot a VM.",
3914 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
3917 additionalProperties
=> 0,
3919 node
=> get_standard_option
('pve-node'),
3920 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
3921 snapname
=> get_standard_option
('pve-snapshot-name'),
3925 description
=> "Save the vmstate",
3930 description
=> "A textual description or comment.",
3936 description
=> "the task ID.",
3941 my $rpcenv = PVE
::RPCEnvironment
::get
();
3943 my $authuser = $rpcenv->get_user();
3945 my $node = extract_param
($param, 'node');
3947 my $vmid = extract_param
($param, 'vmid');
3949 my $snapname = extract_param
($param, 'snapname');
3951 die "unable to use snapshot name 'current' (reserved name)\n"
3952 if $snapname eq 'current';
3954 die "unable to use snapshot name 'pending' (reserved name)\n"
3955 if lc($snapname) eq 'pending';
3958 PVE
::Cluster
::log_msg
('info', $authuser, "snapshot VM $vmid: $snapname");
3959 PVE
::QemuConfig-
>snapshot_create($vmid, $snapname, $param->{vmstate
},
3960 $param->{description
});
3963 return $rpcenv->fork_worker('qmsnapshot', $vmid, $authuser, $realcmd);
3966 __PACKAGE__-
>register_method({
3967 name
=> 'snapshot_cmd_idx',
3968 path
=> '{vmid}/snapshot/{snapname}',
3975 additionalProperties
=> 0,
3977 vmid
=> get_standard_option
('pve-vmid'),
3978 node
=> get_standard_option
('pve-node'),
3979 snapname
=> get_standard_option
('pve-snapshot-name'),
3988 links
=> [ { rel
=> 'child', href
=> "{cmd}" } ],
3995 push @$res, { cmd
=> 'rollback' };
3996 push @$res, { cmd
=> 'config' };
4001 __PACKAGE__-
>register_method({
4002 name
=> 'update_snapshot_config',
4003 path
=> '{vmid}/snapshot/{snapname}/config',
4007 description
=> "Update snapshot metadata.",
4009 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4012 additionalProperties
=> 0,
4014 node
=> get_standard_option
('pve-node'),
4015 vmid
=> get_standard_option
('pve-vmid'),
4016 snapname
=> get_standard_option
('pve-snapshot-name'),
4020 description
=> "A textual description or comment.",
4024 returns
=> { type
=> 'null' },
4028 my $rpcenv = PVE
::RPCEnvironment
::get
();
4030 my $authuser = $rpcenv->get_user();
4032 my $vmid = extract_param
($param, 'vmid');
4034 my $snapname = extract_param
($param, 'snapname');
4036 return if !defined($param->{description
});
4038 my $updatefn = sub {
4040 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4042 PVE
::QemuConfig-
>check_lock($conf);
4044 my $snap = $conf->{snapshots
}->{$snapname};
4046 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4048 $snap->{description
} = $param->{description
} if defined($param->{description
});
4050 PVE
::QemuConfig-
>write_config($vmid, $conf);
4053 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4058 __PACKAGE__-
>register_method({
4059 name
=> 'get_snapshot_config',
4060 path
=> '{vmid}/snapshot/{snapname}/config',
4063 description
=> "Get snapshot configuration",
4065 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback', 'VM.Audit' ], any
=> 1],
4068 additionalProperties
=> 0,
4070 node
=> get_standard_option
('pve-node'),
4071 vmid
=> get_standard_option
('pve-vmid'),
4072 snapname
=> get_standard_option
('pve-snapshot-name'),
4075 returns
=> { type
=> "object" },
4079 my $rpcenv = PVE
::RPCEnvironment
::get
();
4081 my $authuser = $rpcenv->get_user();
4083 my $vmid = extract_param
($param, 'vmid');
4085 my $snapname = extract_param
($param, 'snapname');
4087 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4089 my $snap = $conf->{snapshots
}->{$snapname};
4091 die "snapshot '$snapname' does not exist\n" if !defined($snap);
4096 __PACKAGE__-
>register_method({
4098 path
=> '{vmid}/snapshot/{snapname}/rollback',
4102 description
=> "Rollback VM state to specified snapshot.",
4104 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot', 'VM.Snapshot.Rollback' ], any
=> 1],
4107 additionalProperties
=> 0,
4109 node
=> get_standard_option
('pve-node'),
4110 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4111 snapname
=> get_standard_option
('pve-snapshot-name'),
4116 description
=> "the task ID.",
4121 my $rpcenv = PVE
::RPCEnvironment
::get
();
4123 my $authuser = $rpcenv->get_user();
4125 my $node = extract_param
($param, 'node');
4127 my $vmid = extract_param
($param, 'vmid');
4129 my $snapname = extract_param
($param, 'snapname');
4132 PVE
::Cluster
::log_msg
('info', $authuser, "rollback snapshot VM $vmid: $snapname");
4133 PVE
::QemuConfig-
>snapshot_rollback($vmid, $snapname);
4137 # hold migration lock, this makes sure that nobody create replication snapshots
4138 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
4141 return $rpcenv->fork_worker('qmrollback', $vmid, $authuser, $worker);
4144 __PACKAGE__-
>register_method({
4145 name
=> 'delsnapshot',
4146 path
=> '{vmid}/snapshot/{snapname}',
4150 description
=> "Delete a VM snapshot.",
4152 check
=> ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
4155 additionalProperties
=> 0,
4157 node
=> get_standard_option
('pve-node'),
4158 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4159 snapname
=> get_standard_option
('pve-snapshot-name'),
4163 description
=> "For removal from config file, even if removing disk snapshots fails.",
4169 description
=> "the task ID.",
4174 my $rpcenv = PVE
::RPCEnvironment
::get
();
4176 my $authuser = $rpcenv->get_user();
4178 my $node = extract_param
($param, 'node');
4180 my $vmid = extract_param
($param, 'vmid');
4182 my $snapname = extract_param
($param, 'snapname');
4185 PVE
::Cluster
::log_msg
('info', $authuser, "delete snapshot VM $vmid: $snapname");
4186 PVE
::QemuConfig-
>snapshot_delete($vmid, $snapname, $param->{force
});
4189 return $rpcenv->fork_worker('qmdelsnapshot', $vmid, $authuser, $realcmd);
4192 __PACKAGE__-
>register_method({
4194 path
=> '{vmid}/template',
4198 description
=> "Create a Template.",
4200 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
4201 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
4204 additionalProperties
=> 0,
4206 node
=> get_standard_option
('pve-node'),
4207 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid_stopped
}),
4211 description
=> "If you want to convert only 1 disk to base image.",
4212 enum
=> [PVE
::QemuServer
::Drive
::valid_drive_names
()],
4217 returns
=> { type
=> 'null'},
4221 my $rpcenv = PVE
::RPCEnvironment
::get
();
4223 my $authuser = $rpcenv->get_user();
4225 my $node = extract_param
($param, 'node');
4227 my $vmid = extract_param
($param, 'vmid');
4229 my $disk = extract_param
($param, 'disk');
4231 my $updatefn = sub {
4233 my $conf = PVE
::QemuConfig-
>load_config($vmid);
4235 PVE
::QemuConfig-
>check_lock($conf);
4237 die "unable to create template, because VM contains snapshots\n"
4238 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
4240 die "you can't convert a template to a template\n"
4241 if PVE
::QemuConfig-
>is_template($conf) && !$disk;
4243 die "you can't convert a VM to template if VM is running\n"
4244 if PVE
::QemuServer
::check_running
($vmid);
4247 PVE
::QemuServer
::template_create
($vmid, $conf, $disk);
4250 $conf->{template
} = 1;
4251 PVE
::QemuConfig-
>write_config($vmid, $conf);
4253 return $rpcenv->fork_worker('qmtemplate', $vmid, $authuser, $realcmd);
4256 PVE
::QemuConfig-
>lock_config($vmid, $updatefn);
4260 __PACKAGE__-
>register_method({
4261 name
=> 'cloudinit_generated_config_dump',
4262 path
=> '{vmid}/cloudinit/dump',
4265 description
=> "Get automatically generated cloudinit config.",
4267 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
4270 additionalProperties
=> 0,
4272 node
=> get_standard_option
('pve-node'),
4273 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::QemuServer
::complete_vmid
}),
4275 description
=> 'Config type.',
4277 enum
=> ['user', 'network', 'meta'],
4287 my $conf = PVE
::QemuConfig-
>load_config($param->{vmid
});
4289 return PVE
::QemuServer
::Cloudinit
::dump_cloudinit_config
($conf, $param->{vmid
}, $param->{type
});