diff --git a/images/virt-artifact/patches/031-hotplug-container-disk.patch b/images/virt-artifact/patches/031-hotplug-container-disk.patch index ec7c4e62f..24de0d36a 100644 --- a/images/virt-artifact/patches/031-hotplug-container-disk.patch +++ b/images/virt-artifact/patches/031-hotplug-container-disk.patch @@ -869,10 +869,10 @@ index fa4e86ee17..c40f1fad89 100644 pvcName := storagetypes.PVCNameFromVirtVolume(&volume) diff --git a/pkg/virt-handler/container-disk/hotplug.go b/pkg/virt-handler/container-disk/hotplug.go new file mode 100644 -index 0000000000..553f76cb0a +index 0000000000..fa3a7c39af --- /dev/null +++ b/pkg/virt-handler/container-disk/hotplug.go -@@ -0,0 +1,538 @@ +@@ -0,0 +1,667 @@ +package container_disk + +import ( @@ -1119,20 +1119,11 @@ index 0000000000..553f76cb0a + + for _, volume := range vmi.Spec.Volumes { + if volume.ContainerDisk != nil && volume.ContainerDisk.Hotpluggable { -+ target, err := m.hotplugManager.GetFileSystemDiskTargetPathFromHostView(virtLauncherUID, volume.Name, true) ++ entry, err := m.newMountTargetEntry(vmi, virtLauncherUID, sourceUID, volume.Name) + if err != nil { + return nil, err + } -+ -+ sock, err := m.hotplugPathGetter(vmi, volume.Name, sourceUID) -+ if err != nil { -+ return nil, err -+ } -+ -+ record.MountTargetEntries = append(record.MountTargetEntries, vmiMountTargetEntry{ -+ TargetFile: unsafepath.UnsafeAbsolute(target.Raw()), -+ SocketFile: sock, -+ }) ++ record.MountTargetEntries = append(record.MountTargetEntries, entry) + } + } + @@ -1189,24 +1180,131 @@ index 0000000000..553f76cb0a + return disksInfo, nil +} + ++func (m *hotplugMounter) newMountTargetEntry( ++ vmi *v1.VirtualMachineInstance, ++ virtLauncherUID, sourceUID types.UID, ++ volumeName string, ++) (vmiMountTargetEntry, error) { ++ targetFile, err := m.getTarget(virtLauncherUID, volumeName) ++ if err != nil { ++ return vmiMountTargetEntry{}, err ++ } ++ ++ sock, err := m.hotplugPathGetter(vmi, volumeName, sourceUID) ++ if err != nil { ++ return vmiMountTargetEntry{}, err ++ } ++ return vmiMountTargetEntry{ ++ TargetFile: targetFile, ++ SocketFile: sock, ++ }, nil ++} ++ ++func (m *hotplugMounter) getTarget(virtLauncherUID types.UID, volumeName string) (string, error) { ++ target, err := m.hotplugManager.GetFileSystemDiskTargetPathFromHostView(virtLauncherUID, volumeName, true) ++ if err != nil { ++ return "", err ++ } ++ return unsafepath.UnsafeAbsolute(target.Raw()), nil ++} ++ ++func (m *hotplugMounter) getMountedVolumesInWorld(vmi *v1.VirtualMachineInstance, virtLauncherUID types.UID) ([]vmiMountTargetEntry, error) { ++ path, err := m.hotplugManager.GetHotplugTargetPodPathOnHost(virtLauncherUID) ++ if err != nil { ++ return nil, err ++ } ++ rawPath := unsafepath.UnsafeAbsolute(path.Raw()) ++ entries, err := os.ReadDir(rawPath) ++ if err != nil { ++ return nil, err ++ } ++ var volumes []string ++ for _, entry := range entries { ++ info, err := entry.Info() ++ if err != nil { ++ return nil, err ++ } ++ if info.IsDir() { ++ continue ++ } ++ if strings.HasSuffix(entry.Name(), ".img") { ++ name := strings.TrimSuffix(entry.Name(), ".img") ++ volumes = append(volumes, name) ++ } ++ } ++ var mountedVolumes []vmiMountTargetEntry ++ for _, v := range volumes { ++ mounted, err := m.IsMounted(vmi, v) ++ if err != nil { ++ return nil, err ++ } ++ if mounted { ++ target, err := m.getTarget(virtLauncherUID, v) ++ if err != nil { ++ return nil, err ++ } ++ mountedVolumes = append(mountedVolumes, vmiMountTargetEntry{ ++ TargetFile: target, ++ }) ++ } ++ } ++ return mountedVolumes, nil ++} ++ ++func (m *hotplugMounter) mergeMountEntries(r1, r2 []vmiMountTargetEntry) []vmiMountTargetEntry { ++ targetSocket := make(map[string]string) ++ ++ sortTargetSocket := func(r []vmiMountTargetEntry) { ++ for _, entry := range r { ++ socket := targetSocket[entry.TargetFile] ++ if socket == "" { ++ targetSocket[entry.TargetFile] = entry.SocketFile ++ } ++ } ++ } ++ sortTargetSocket(r1) ++ sortTargetSocket(r2) ++ ++ newRecords := make([]vmiMountTargetEntry, len(targetSocket)) ++ count := 0 ++ for t, s := range targetSocket { ++ newRecords[count] = vmiMountTargetEntry{ ++ TargetFile: t, ++ SocketFile: s, ++ } ++ count++ ++ } ++ ++ return newRecords ++} ++ +func (m *hotplugMounter) Umount(vmi *v1.VirtualMachineInstance) error { + record, err := m.getMountTargetRecord(vmi) + if err != nil { + return err -+ } else if record == nil { -+ // no entries to unmount ++ } ++ ++ worldEntries, err := m.getMountedVolumesInWorld(vmi, m.findVirtlauncherUID(vmi)) ++ if err != nil { ++ return fmt.Errorf("failed to get world entries: %w", err) ++ } ++ var recordMountTargetEntries []vmiMountTargetEntry ++ if record != nil { ++ recordMountTargetEntries = record.MountTargetEntries ++ } + ++ mountEntries := m.mergeMountEntries(recordMountTargetEntries, worldEntries) ++ ++ if len(mountEntries) == 0 { + log.DefaultLogger().Object(vmi).Infof("No container disk mount entries found to unmount") + return nil + } + -+ entriesForDelete := make(map[vmiMountTargetEntry]struct{}) ++ entriesTargetForDelete := make(map[string]struct{}) ++ ++ for _, r := range mountEntries { ++ name := extractNameFromTarget(r.TargetFile) + -+ for _, r := range record.MountTargetEntries { -+ name, err := extractNameFromSocket(r.SocketFile) -+ if err != nil { -+ return err -+ } + needUmount := true + for _, v := range vmi.Status.VolumeStatus { + if v.Name == name { @@ -1217,7 +1315,7 @@ index 0000000000..553f76cb0a + file, err := safepath.NewFileNoFollow(r.TargetFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { -+ entriesForDelete[r] = struct{}{} ++ entriesTargetForDelete[r.TargetFile] = struct{}{} + continue + } + return fmt.Errorf(failedCheckMountPointFmt, r.TargetFile, err) @@ -1228,7 +1326,10 @@ index 0000000000..553f76cb0a + return fmt.Errorf(failedCheckMountPointFmt, r.TargetFile, err) + } + if !mounted { -+ entriesForDelete[r] = struct{}{} ++ if err = safepath.UnlinkAtNoFollow(file.Path()); err != nil { ++ return fmt.Errorf("failed to delete file %s: %w", file.Path(), err) ++ } ++ entriesTargetForDelete[r.TargetFile] = struct{}{} + continue + } + // #nosec No risk for attacket injection. Parameters are predefined strings @@ -1236,12 +1337,15 @@ index 0000000000..553f76cb0a + if err != nil { + return fmt.Errorf(failedUnmountFmt, file, string(out), err) + } -+ entriesForDelete[r] = struct{}{} ++ if err = safepath.UnlinkAtNoFollow(file.Path()); err != nil { ++ return fmt.Errorf("failed to delete file %s: %w", file.Path(), err) ++ } ++ entriesTargetForDelete[r.TargetFile] = struct{}{} + } + } -+ newEntries := make([]vmiMountTargetEntry, 0, len(record.MountTargetEntries)-len(entriesForDelete)) -+ for _, entry := range record.MountTargetEntries { -+ if _, found := entriesForDelete[entry]; found { ++ newEntries := make([]vmiMountTargetEntry, 0, len(recordMountTargetEntries)-len(entriesTargetForDelete)) ++ for _, entry := range recordMountTargetEntries { ++ if _, found := entriesTargetForDelete[entry.TargetFile]; found { + continue + } + newEntries = append(newEntries, entry) @@ -1260,6 +1364,12 @@ index 0000000000..553f76cb0a + return "", fmt.Errorf("name not found in path") +} + ++func extractNameFromTarget(targetFile string) string { ++ filename := filepath.Base(targetFile) ++ name := strings.TrimSuffix(filename, filepath.Ext(filename)) ++ return name ++} ++ +func (m *hotplugMounter) UmountAll(vmi *v1.VirtualMachineInstance) error { + if vmi.UID == "" { + return nil @@ -1268,15 +1378,27 @@ index 0000000000..553f76cb0a + record, err := m.getMountTargetRecord(vmi) + if err != nil { + return err -+ } else if record == nil { -+ // no entries to unmount ++ } + ++ worldEntries, err := m.getMountedVolumesInWorld(vmi, m.findVirtlauncherUID(vmi)) ++ if err != nil { ++ return fmt.Errorf("failed to get world entries: %w", err) ++ } ++ var recordMountTargetEntries []vmiMountTargetEntry ++ if record != nil { ++ recordMountTargetEntries = record.MountTargetEntries ++ } ++ ++ mountEntries := m.mergeMountEntries(recordMountTargetEntries, worldEntries) ++ ++ if len(mountEntries) == 0 { + log.DefaultLogger().Object(vmi).Infof("No container disk mount entries found to unmount") + return nil + } + + log.DefaultLogger().Object(vmi).Infof("Found container disk mount entries") -+ for _, entry := range record.MountTargetEntries { ++ ++ for _, entry := range mountEntries { + log.DefaultLogger().Object(vmi).Infof("Looking to see if containerdisk is mounted at path %s", entry.TargetFile) + file, err := safepath.NewFileNoFollow(entry.TargetFile) + if err != nil { @@ -1295,6 +1417,13 @@ index 0000000000..553f76cb0a + if err != nil { + return fmt.Errorf(failedUnmountFmt, file, string(out), err) + } ++ if err = safepath.UnlinkAtNoFollow(file.Path()); err != nil { ++ return fmt.Errorf("failed to delete file %s: %w", file.Path(), err) ++ } ++ } else { ++ if err = safepath.UnlinkAtNoFollow(file.Path()); err != nil { ++ return fmt.Errorf("failed to delete file %s: %w", file.Path(), err) ++ } + } + } + err = m.deleteMountTargetRecord(vmi)