]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/API2/Qemu.pm
config: define machine schema as property-string
[qemu-server.git] / PVE / API2 / Qemu.pm
index 1ff5abe40edf44274f513a7ca375a45e1772cbfd..f3ce83d6f9d4126967a5034e36621d2fae12948c 100644 (file)
@@ -316,13 +316,26 @@ my $import_from_volid = 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, $arch, $storecfg, $vmid, $pool, $settings, $default_storage) = @_;
+my sub create_disks : prototype($$$$$$$$$$) {
+    my (
+       $rpcenv,
+       $authuser,
+       $conf,
+       $arch,
+       $storecfg,
+       $vmid,
+       $pool,
+       $settings,
+       $default_storage,
+       $is_live_import,
+    ) = @_;
 
     my $vollist = [];
 
     my $res = {};
 
+    my $live_import_mapping = {};
+
     my $code = sub {
        my ($ds, $disk) = @_;
 
@@ -368,52 +381,82 @@ my $create_disks = sub {
            my ($storeid, $size) = ($2 || $default_storage, $3);
            die "no storage ID specified (and no default storage)\n" if !$storeid;
 
+           $size = PVE::Tools::convert_size($size, 'gb' => 'kb'); # vdisk_alloc uses kb
+
+           my $live_import = $is_live_import && $ds ne 'efidisk0';
+           my $needs_creation = 1;
+
            if (my $source = delete $disk->{'import-from'}) {
                my $dst_volid;
 
+               $needs_creation = $live_import;
+
                if (PVE::Storage::parse_volume_id($source, 1)) { # PVE-managed volume
-                   my $dest_info = {
-                       vmid => $vmid,
-                       drivename => $ds,
-                       storage => $storeid,
-                       format => $disk->{format},
-                   };
+                   if ($live_import && $ds ne 'efidisk0') {
+                       my $path = PVE::Storage::path($storecfg, $source)
+                           or die "failed to get a path for '$source'\n";
+                       $source = $path;
+                       ($size, my $source_format) = PVE::Storage::file_size_info($source);
+                       die "could not get file size of $source\n" if !$size;
+                       $live_import_mapping->{$ds} = {
+                           path => $source,
+                           format => $source_format,
+                       };
+                   } else {
+                       my $dest_info = {
+                           vmid => $vmid,
+                           drivename => $ds,
+                           storage => $storeid,
+                           format => $disk->{format},
+                       };
 
-                   $dest_info->{efisize} = PVE::QemuServer::get_efivars_size($conf, $disk)
-                       if $ds eq 'efidisk0';
+                       $dest_info->{efisize} = PVE::QemuServer::get_efivars_size($conf, $disk)
+                           if $ds eq 'efidisk0';
 
-                   ($dst_volid, $size) = eval {
-                       $import_from_volid->($storecfg, $source, $dest_info, $vollist);
-                   };
-                   die "cannot import from '$source' - $@" if $@;
+                       ($dst_volid, $size) = eval {
+                           $import_from_volid->($storecfg, $source, $dest_info, $vollist);
+                       };
+                       die "cannot import from '$source' - $@" if $@;
+                   }
                } else {
                    $source = PVE::Storage::abs_filesystem_path($storecfg, $source, 1);
-                   $size = PVE::Storage::file_size_info($source);
+                   ($size, my $source_format) = PVE::Storage::file_size_info($source);
                    die "could not get file size of $source\n" if !$size;
 
-                   (undef, $dst_volid) = PVE::QemuServer::ImportDisk::do_import(
-                       $source,
-                       $vmid,
-                       $storeid,
-                       {
-                           drive_name => $ds,
-                           format => $disk->{format},
-                           'skip-config-update' => 1,
-                       },
-                   );
-                   push @$vollist, $dst_volid;
+                   if ($live_import && $ds ne 'efidisk0') {
+                       $live_import_mapping->{$ds} = {
+                           path => $source,
+                           format => $source_format,
+                       };
+                   } else {
+                       (undef, $dst_volid) = PVE::QemuServer::ImportDisk::do_import(
+                           $source,
+                           $vmid,
+                           $storeid,
+                           {
+                               drive_name => $ds,
+                               format => $disk->{format},
+                               'skip-config-update' => 1,
+                           },
+                       );
+                       push @$vollist, $dst_volid;
+                   }
                }
 
-               $disk->{file} = $dst_volid;
-               $disk->{size} = $size;
-               delete $disk->{format}; # no longer needed
-               $res->{$ds} = PVE::QemuServer::print_drive($disk);
-           } else {
+               if ($needs_creation) {
+                   $size = PVE::Tools::convert_size($size, 'b' => 'kb'); # vdisk_alloc uses kb
+               } else {
+                   $disk->{file} = $dst_volid;
+                   $disk->{size} = $size;
+                   delete $disk->{format}; # no longer needed
+                   $res->{$ds} = PVE::QemuServer::print_drive($disk);
+               }
+           }
+
+           if ($needs_creation) {
                my $defformat = PVE::Storage::storage_default_format($storecfg, $storeid);
                my $fmt = $disk->{format} || $defformat;
 
-               $size = PVE::Tools::convert_size($size, 'gb' => 'kb'); # vdisk_alloc uses kb
-
                my $volid;
                if ($ds eq 'efidisk0') {
                    my $smm = PVE::QemuServer::Machine::machine_type_is_q35($conf);
@@ -474,7 +517,10 @@ my $create_disks = sub {
        die $err;
     }
 
-    return ($vollist, $res);
+    # don't return empty import mappings
+    $live_import_mapping = undef if !%$live_import_mapping;
+
+    return ($vollist, $res, $live_import_mapping);
 };
 
 my $check_cpu_model_access = sub {
@@ -705,7 +751,7 @@ sub assert_scsi_feature_compatibility {
     my $machine_type = PVE::QemuServer::get_vm_machine($conf, undef, $conf->{arch});
     my $machine_version = PVE::QemuServer::Machine::extract_version(
        $machine_type, PVE::QemuServer::kvm_user_version());
-    my $drivetype = PVE::QemuServer::Drive::get_scsi_devicetype(
+    my $drivetype = PVE::QemuServer::Drive::get_scsi_device_type(
        $drive, $storecfg, $machine_version);
 
     if ($drivetype ne 'hd' && $drivetype ne 'cd') {
@@ -794,7 +840,6 @@ my $parse_restore_archive = sub {
     return $res;
 };
 
-
 __PACKAGE__->register_method({
     name => 'create_vm',
     path => '',
@@ -842,8 +887,7 @@ __PACKAGE__->register_method({
                'live-restore' => {
                    optional => 1,
                    type => 'boolean',
-                   description => "Start the VM immediately from the backup and restore in background. PBS only.",
-                   requires => 'archive',
+                   description => "Start the VM immediately while importing or restoring in the background.",
                },
                pool => {
                    optional => 1,
@@ -1034,6 +1078,8 @@ __PACKAGE__->register_method({
        };
 
        my $createfn = sub {
+           my $live_import_mapping = {};
+
            # ensure no old replication state are exists
            PVE::ReplicationState::delete_guest_states($vmid);
 
@@ -1041,7 +1087,6 @@ __PACKAGE__->register_method({
                my $conf = $param;
                my $arch = PVE::QemuServer::get_vm_arch($conf);
 
-
                for my $opt (sort keys $param->%*) {
                    next if $opt !~ m/^scsi\d+$/;
                    assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt});
@@ -1051,7 +1096,7 @@ __PACKAGE__->register_method({
 
                my $vollist = [];
                eval {
-                   ($vollist, my $created_opts) = $create_disks->(
+                   ($vollist, my $created_opts, $live_import_mapping) = create_disks(
                        $rpcenv,
                        $authuser,
                        $conf,
@@ -1061,6 +1106,7 @@ __PACKAGE__->register_method({
                        $pool,
                        $param,
                        $storage,
+                       $live_restore,
                    );
                    $conf->{$_} = $created_opts->{$_} for keys $created_opts->%*;
 
@@ -1081,16 +1127,19 @@ __PACKAGE__->register_method({
                        $conf->{vmgenid} = PVE::QemuServer::generate_uuid();
                    }
 
-                   my $machine = $conf->{machine};
+                   my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine});
+                   my $machine = $machine_conf->{type};
                    if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
                        # always pin Windows' machine version on create, they get to easily confused
                        if (PVE::QemuServer::Helpers::windows_version($conf->{ostype})) {
-                           $conf->{machine} = PVE::QemuServer::windows_get_pinned_machine_version($machine);
+                           $machine_conf->{type} = PVE::QemuServer::windows_get_pinned_machine_version($machine);
+                           $conf->{machine} = PVE::QemuServer::Machine::print_machine($machine_conf);
                        }
                    }
 
-                   PVE::QemuConfig->write_config($vmid, $conf);
+                   $conf->{lock} = 'import' if $live_import_mapping;
 
+                   PVE::QemuConfig->write_config($vmid, $conf);
                };
                my $err = $@;
 
@@ -1109,10 +1158,13 @@ __PACKAGE__->register_method({
 
            PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
 
-           if ($start_after_create) {
+           if ($start_after_create && !$live_restore) {
                print "Execute autostart\n";
                eval { PVE::API2::Qemu->vm_start({vmid => $vmid, node => $node}) };
                warn $@ if $@;
+               return;
+           } else {
+               return $live_import_mapping;
            }
        };
 
@@ -1137,7 +1189,9 @@ __PACKAGE__->register_method({
        } else {
            $worker_name = 'qmcreate';
            $code = sub {
-               eval { $createfn->() };
+               # If a live import was requested the create function returns
+               # the mapping for the startup.
+               my $live_import_mapping = eval { $createfn->() };
                if (my $err = $@) {
                    eval {
                        my $conffile = PVE::QemuConfig->config_file($vmid);
@@ -1146,6 +1200,21 @@ __PACKAGE__->register_method({
                    warn $@ if $@;
                    die $err;
                }
+
+               if ($live_import_mapping) {
+                   my $import_options = {
+                       bwlimit => $bwlimit,
+                       live => 1,
+                   };
+
+                   my $conf = PVE::QemuConfig->load_config($vmid);
+                   PVE::QemuServer::live_import_from_files(
+                       $live_import_mapping,
+                       $vmid,
+                       $conf,
+                       $import_options,
+                   );
+               }
            };
        }
 
@@ -1870,7 +1939,7 @@ my $update_vm_api  = sub {
                    assert_scsi_feature_compatibility($opt, $conf, $storecfg, $param->{$opt})
                        if $opt =~ m/^scsi\d+$/;
 
-                   my (undef, $created_opts) = $create_disks->(
+                   my (undef, $created_opts) = create_disks(
                        $rpcenv,
                        $authuser,
                        $conf,
@@ -1879,6 +1948,8 @@ my $update_vm_api  = sub {
                        $vmid,
                        undef,
                        {$opt => $param->{$opt}},
+                       undef,
+                       undef,
                    );
                    $conf->{pending}->{$_} = $created_opts->{$_} for keys $created_opts->%*;
 
@@ -1927,6 +1998,9 @@ my $update_vm_api  = sub {
                        );
                    }
                    $conf->{pending}->{$opt} = $param->{$opt};
+               } elsif ($opt eq 'machine') {
+                   my $machine_conf = PVE::QemuServer::Machine::parse_machine($param->{$opt});
+                   $conf->{pending}->{$opt} = $param->{$opt};
                } else {
                    $conf->{pending}->{$opt} = $param->{$opt};
 
@@ -3820,11 +3894,12 @@ __PACKAGE__->register_method({
                PVE::QemuServer::create_ifaces_ipams_ips($newconf, $newid);
 
                if ($target) {
-                   # always deactivate volumes - avoid lvm LVs to be active on several nodes
-                   eval {
-                       PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
-                   };
-                   log_warn($@) if ($@);
+                   if (!$running) {
+                       # always deactivate volumes – avoids that LVM LVs are active on several nodes
+                       eval { PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) };
+                       # but only warn when that fails (e.g., parallel clones keeping them active)
+                       log_warn($@) if $@;
+                   }
 
                    PVE::Storage::deactivate_volumes($storecfg, $newvollist);