]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
code cleanup
[qemu-server.git] / PVE / API2 / Qemu.pm
index 52f4a5fd518e0a1d630ff4966648a2a5d7357171..d8c9726620bb1a71c8417566a3ba94fa076a71cc 100644 (file)
@@ -130,7 +130,7 @@ my $check_storage_access_clone = sub {
 # Note: $pool is only needed when creating a VM, because pool permissions
 # are automatically inherited if VM already exists inside a pool.
 my $create_disks = sub {
-    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
+    my ($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
 
     my $vollist = [];
 
@@ -156,8 +156,8 @@ my $create_disks = sub {
            }else{
                $fmt = 'raw';
            }
-           # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
-           my $cloudinit_iso_size = 5; # in MB
+           # Initial disk created with 4MB, every time it is regenerated the disk is aligned to 4MB again.
+           my $cloudinit_iso_size = 4; # in MB
            my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, 
                                                  $fmt, $name, $cloudinit_iso_size*1024);
            $disk->{file} = $volid;
@@ -175,7 +175,7 @@ my $create_disks = sub {
 
            my $volid;
            if ($ds eq 'efidisk0') {
-               ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt);
+               ($volid, $size) = PVE::QemuServer::create_efidisk($storecfg, $storeid, $vmid, $fmt, $arch);
            } else {
                $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $size);
            }
@@ -292,6 +292,7 @@ my $diskoptions = {
 };
 
 my $cloudinitoptions = {
+    cicustom => 1,
     cipassword => 1,
     citype => 1,
     ciuser => 1,
@@ -439,10 +440,11 @@ __PACKAGE__->register_method({
                    description => "Add the VM to the specified pool.",
                },
                bwlimit => {
-                   description => "Override i/o bandwidth limit (in KiB/s).",
+                   description => "Override I/O bandwidth limit (in KiB/s).",
                    optional => 1,
                    type => 'integer',
                    minimum => '0',
+                   default => 'restore limit from datacenter or storage config',
                },
                start => {
                    optional => 1,
@@ -585,9 +587,11 @@ __PACKAGE__->register_method({
 
                my $conf = $param;
 
+               my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
+
                eval {
 
-                   $vollist = &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage);
+                   $vollist = &$create_disks($rpcenv, $authuser, $conf, $arch, $storecfg, $vmid, $pool, $param, $storage);
 
                    if (!$conf->{bootdisk}) {
                        my $firstdisk = PVE::QemuServer::resolve_first_disk($conf);
@@ -599,7 +603,7 @@ __PACKAGE__->register_method({
                        $conf->{smbios1} = PVE::QemuServer::generate_smbios1_uuid();
                    }
 
-                   if (!defined($conf->{vmgenid}) || $conf->{vmgenid} eq '1') {
+                   if ((!defined($conf->{vmgenid}) || $conf->{vmgenid} eq '1') && $arch ne 'aarch64') {
                        $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
                    }
 
@@ -646,8 +650,7 @@ __PACKAGE__->register_method({
                if (my $err = $@) {
                    eval {
                        my $conffile = PVE::QemuConfig->config_file($vmid);
-                       unlink($conffile)
-                           or die "failed to remove config file: $@\n";
+                       unlink($conffile) or die "failed to remove config file: $!\n";
                    };
                    warn $@ if $@;
                    die $err;
@@ -822,12 +825,20 @@ __PACKAGE__->register_method({
        properties => {
            node => get_standard_option('pve-node'),
            vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
-            current => {
-                description => "Get current values (instead of pending values).",
-                optional => 1,
+           current => {
+               description => "Get current values (instead of pending values).",
+               optional => 1,
                default => 0,
                type => 'boolean',
-            },
+           },
+           snapshot => get_standard_option('pve-snapshot-name', {
+               description => "Fetch config values from given snapshot.",
+               optional => 1,
+               completion => sub {
+                   my ($cmd, $pname, $cur, $args) = @_;
+                   PVE::QemuConfig->snapshot_list($args->[0]);
+               },
+           }),
        },
     },
     returns => {
@@ -845,6 +856,15 @@ __PACKAGE__->register_method({
 
        my $conf = PVE::QemuConfig->load_config($param->{vmid});
 
+       if (my $snapname = $param->{snapshot}) {
+           my $snapshot = $conf->{snapshots}->{$snapname};
+           die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+           $snapshot->{digest} = $conf->{digest}; # keep file digest for API
+
+           $conf = $snapshot;
+       }
+
        delete $conf->{snapshots};
 
        if (!$param->{current}) {
@@ -1099,6 +1119,9 @@ my $update_vm_api  = sub {
            if ($param->{$opt} eq '1') {
                $param->{$opt} = PVE::QemuServer::generate_uuid();
            }
+       } elsif ($opt eq 'hookscript') {
+           eval { PVE::GuestHelpers::check_hookscript($param->{$opt}, $storecfg); };
+           raise_param_exc({ $opt => $@ }) if $@;
        }
     }
 
@@ -1178,6 +1201,8 @@ my $update_vm_api  = sub {
                $conf = PVE::QemuConfig->load_config($vmid); # update/reload
                next if defined($conf->{pending}->{$opt}) && ($param->{$opt} eq $conf->{pending}->{$opt}); # skip if nothing changed
 
+               my ($arch, undef) = PVE::QemuServer::get_basic_machine_info($conf);
+
                if (PVE::QemuServer::is_valid_drivename($opt)) {
                    my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
                    # FIXME: cloudinit: CDROM or Disk?
@@ -1189,7 +1214,7 @@ my $update_vm_api  = sub {
                    PVE::QemuServer::vmconfig_register_unused_drive($storecfg, $vmid, $conf, PVE::QemuServer::parse_drive($opt, $conf->{pending}->{$opt}))
                        if defined($conf->{pending}->{$opt});
 
-                   &$create_disks($rpcenv, $authuser, $conf->{pending}, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
+                   &$create_disks($rpcenv, $authuser, $conf->{pending}, $arch, $storecfg, $vmid, undef, {$opt => $param->{$opt}});
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
                }
@@ -1521,6 +1546,7 @@ __PACKAGE__->register_method({
        my $websocket = $param->{websocket};
 
        my $conf = PVE::QemuConfig->load_config($vmid, $node); # check if VM exists
+       my $use_serial = ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/));
 
        my $authpath = "/vms/$vmid";
 
@@ -1529,13 +1555,14 @@ __PACKAGE__->register_method({
        $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
            if !$sslcert;
 
-       my ($remip, $family);
+       my $family;
        my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+           (undef, $family) = PVE::Cluster::remote_node_ip($node);
+           my $sshinfo = PVE::Cluster::get_ssh_info($node);
            # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
-           $remcmd = ['/usr/bin/ssh', '-e', 'none', '-T', '-o', 'BatchMode=yes', $remip];
+           $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, $use_serial ? '-t' : '-T');
        } else {
            $family = PVE::Tools::get_host_address_family($node);
        }
@@ -1551,8 +1578,7 @@ __PACKAGE__->register_method({
 
            my $cmd;
 
-           if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
-
+           if ($use_serial) {
 
                my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
 
@@ -1669,19 +1695,20 @@ __PACKAGE__->register_method({
 
        my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
 
-       my ($remip, $family);
+       my $family;
+       my $remcmd = [];
 
        if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
-           ($remip, $family) = PVE::Cluster::remote_node_ip($node);
+           (undef, $family) = PVE::Cluster::remote_node_ip($node);
+           my $sshinfo = PVE::Cluster::get_ssh_info($node);
+           $remcmd = PVE::Cluster::ssh_info_to_command($sshinfo, '-t');
+           push @$remcmd, '--';
        } else {
            $family = PVE::Tools::get_host_address_family($node);
        }
 
        my $port = PVE::Tools::next_vnc_port($family);
 
-       my $remcmd = $remip ?
-           ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
-
        my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
        push @$termcmd, '-iface', $serial if $serial;
 
@@ -2323,6 +2350,18 @@ __PACKAGE__->register_method({
            vmid => get_standard_option('pve-vmid',
                                        { completion => \&PVE::QemuServer::complete_vmid_running }),
            skiplock => get_standard_option('skiplock'),
+           todisk => {
+               type => 'boolean',
+               default => 0,
+               optional => 1,
+               description => 'If set, suspends the VM to disk. Will be resumed on next VM start.',
+           },
+           statestorage => get_standard_option('pve-storage-id', {
+               description => "The storage for the VM state",
+               requires => 'todisk',
+               optional => 1,
+               completion => \&PVE::Storage::complete_storage_enabled,
+           }),
        },
     },
     returns => {
@@ -2339,23 +2378,32 @@ __PACKAGE__->register_method({
 
        my $vmid = extract_param($param, 'vmid');
 
+       my $todisk = extract_param($param, 'todisk') // 0;
+
+       my $statestorage = extract_param($param, 'statestorage');
+
        my $skiplock = extract_param($param, 'skiplock');
        raise_param_exc({ skiplock => "Only root may use this option." })
            if $skiplock && $authuser ne 'root@pam';
 
        die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid);
 
+       die "Cannot suspend HA managed VM to disk\n"
+           if $todisk && PVE::HA::Config::vm_is_ha_managed($vmid);
+
+       my $taskname = $todisk ? 'qmsuspend' : 'qmpause';
+
        my $realcmd = sub {
            my $upid = shift;
 
            syslog('info', "suspend VM $vmid: $upid\n");
 
-           PVE::QemuServer::vm_suspend($vmid, $skiplock);
+           PVE::QemuServer::vm_suspend($vmid, $skiplock, $todisk, $statestorage);
 
            return;
        };
 
-       return $rpcenv->fork_worker('qmsuspend', $vmid, $authuser, $realcmd);
+       return $rpcenv->fork_worker($taskname, $vmid, $authuser, $realcmd);
     }});
 
 __PACKAGE__->register_method({
@@ -2399,14 +2447,28 @@ __PACKAGE__->register_method({
 
        my $nocheck = extract_param($param, 'nocheck');
 
-       die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid, $nocheck);
+       my $to_disk_suspended;
+       eval {
+           PVE::QemuConfig->lock_config($vmid, sub {
+               my $conf = PVE::QemuConfig->load_config($vmid);
+               $to_disk_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
+           });
+       };
+
+       die "VM $vmid not running\n"
+           if !$to_disk_suspended && !PVE::QemuServer::check_running($vmid, $nocheck);
 
        my $realcmd = sub {
            my $upid = shift;
 
            syslog('info', "resume VM $vmid: $upid\n");
 
-           PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
+           if (!$to_disk_suspended) {
+               PVE::QemuServer::vm_resume($vmid, $skiplock, $nocheck);
+           } else {
+               my $storecfg = PVE::Storage::config();
+               PVE::QemuServer::vm_start($storecfg, $vmid, undef, $skiplock);
+           }
 
            return;
        };
@@ -2590,6 +2652,13 @@ __PACKAGE__->register_method({
                description => "Target node. Only allowed if the original VM is on shared storage.",
                optional => 1,
            }),
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'clone limit from datacenter or storage config',
+           },
         },
     },
     returns => {
@@ -2775,6 +2844,8 @@ __PACKAGE__->register_method({
 
                    PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
 
+                   my $bwlimit = extract_param($param, 'bwlimit');
+
                    my $total_jobs = scalar(keys %{$drives});
                    my $i = 1;
 
@@ -2782,9 +2853,14 @@ __PACKAGE__->register_method({
                        my $drive = $drives->{$opt};
                        my $skipcomplete = ($total_jobs != $i); # finish after last drive
 
+                       my $src_sid = PVE::Storage::parse_volume_id($drive->{file});
+                       my $storage_list = [ $src_sid ];
+                       push @$storage_list, $storage if defined($storage);
+                       my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', $storage_list, $bwlimit);
+
                        my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $opt, $drive, $snapname,
                                                                   $newid, $storage, $format, $fullclone->{$opt}, $newvollist,
-                                                                  $jobs, $skipcomplete, $oldconf->{agent});
+                                                                  $jobs, $skipcomplete, $oldconf->{agent}, $clonelimit);
 
                        $newconf->{$opt} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
@@ -2890,6 +2966,13 @@ __PACKAGE__->register_method({
                maxLength => 40,
                optional => 1,
            },
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'move limit from datacenter or storage config',
+           },
        },
     },
     returns => {
@@ -2967,8 +3050,11 @@ __PACKAGE__->register_method({
                    warn "moving disk with snapshots, snapshots will not be moved!\n"
                        if $snapshotted;
 
+                   my $bwlimit = extract_param($param, 'bwlimit');
+                   my $movelimit = PVE::Storage::get_bandwidth_limit('move', [$oldstoreid, $storeid], $bwlimit);
+
                    my $newdrive = PVE::QemuServer::clone_disk($storecfg, $vmid, $running, $disk, $drive, undef,
-                                                              $vmid, $storeid, $format, 1, $newvollist);
+                                                              $vmid, $storeid, $format, 1, $newvollist, undef, undef, undef, $movelimit);
 
                    $conf->{$disk} = PVE::QemuServer::print_drive($vmid, $newdrive);
 
@@ -3065,6 +3151,13 @@ __PACKAGE__->register_method({
                optional => 1,
                completion => \&PVE::QemuServer::complete_storage,
             }),
+           bwlimit => {
+               description => "Override I/O bandwidth limit (in KiB/s).",
+               optional => 1,
+               type => 'integer',
+               minimum => '0',
+               default => 'migrate limit from datacenter or storage config',
+           },
        },
     },
     returns => {