-
Notifications
You must be signed in to change notification settings - Fork 10
Fix: size limit with disk image set #777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e3bdfcd
a298ac8
4aee0a9
b3f5027
e7f27fb
4f2d7d8
0626b46
e640687
7866ea4
4dd1f2d
8aac251
6f94c93
5fcaca3
2dc86a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and IronCore contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package localdisk_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| ) | ||
|
|
||
| func TestLocalDisk(t *testing.T) { | ||
| RegisterFailHandler(Fail) | ||
| RunSpecs(t, "LocalDisk Suite") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // SPDX-FileCopyrightText: SAP SE or an SAP affiliate company and IronCore contributors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package localdisk_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "github.com/ironcore-dev/libvirt-provider/api" | ||
| "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume" | ||
| "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume/localdisk" | ||
| "github.com/ironcore-dev/libvirt-provider/internal/raw" | ||
| apiutils "github.com/ironcore-dev/provider-utils/apiutils/api" | ||
| ociutils "github.com/ironcore-dev/provider-utils/ociutils/oci" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
| ) | ||
|
|
||
| const ( | ||
| machineID = "1a2b3c" | ||
| volumeName = "disk-1" | ||
| imageSize int64 = 4096 | ||
| grownSize int64 = imageSize * 4 | ||
| negativeSize int64 = -1 | ||
| ) | ||
|
|
||
| var _ = Describe("Plugin", func() { | ||
| Describe("Apply", func() { | ||
| var volumeDir, diskFile string | ||
| var plugin volume.Plugin | ||
|
|
||
| BeforeEach(func() { | ||
| volumeDir = GinkgoT().TempDir() | ||
| diskFile = filepath.Join(volumeDir, "disk.raw") | ||
|
|
||
| plugin = localdisk.NewPlugin(raw.Exec{}, fakeImageCache{rootFSPath: writeImageRootFS()}) | ||
| Expect(plugin.Init(fakeHost{volumeDir: volumeDir})).To(Succeed()) | ||
| }) | ||
|
|
||
| When("local disk with image", func() { | ||
| It("rejects a negative size", func(ctx SpecContext) { | ||
| _, err := plugin.Apply(ctx, imageBackedVolume(negativeSize), testMachine()) | ||
|
|
||
| Expect(err).To(HaveOccurred()) | ||
|
|
||
| Expect(diskFile).ToNot(BeAnExistingFile(), | ||
| "disk with the wrong size gets stuck and will not be fixed on reapply") | ||
| }) | ||
|
|
||
| It("keeps the image size when no size is set", func(ctx SpecContext) { | ||
| vol, err := plugin.Apply(ctx, imageBackedVolume(0), testMachine()) | ||
|
|
||
| Expect(err).ToNot(HaveOccurred()) | ||
| Expect(vol.EffectiveStorageBytesSize).To(Equal(imageSize)) | ||
| }) | ||
|
|
||
| It("grows the disk to the configured size", func(ctx SpecContext) { | ||
| vol, err := plugin.Apply(ctx, imageBackedVolume(grownSize), testMachine()) | ||
|
|
||
| Expect(err).ToNot(HaveOccurred()) | ||
| Expect(vol.EffectiveStorageBytesSize).To(Equal(grownSize)) | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| func imageBackedVolume(size int64) *api.VolumeSpec { | ||
| image := "example.org/os-foo/gardenlinux:latest" | ||
|
|
||
| return &api.VolumeSpec{ | ||
| Name: volumeName, | ||
| LocalDisk: &api.LocalDiskSpec{ | ||
| Size: size, | ||
| Image: &image, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func testMachine() *api.Machine { | ||
| return &api.Machine{Metadata: apiutils.Metadata{ID: machineID}} | ||
| } | ||
|
|
||
| func writeImageRootFS() string { | ||
| GinkgoHelper() | ||
|
|
||
| rootFS := filepath.Join(GinkgoT().TempDir(), "rootfs.raw") | ||
|
|
||
| Expect(os.WriteFile(rootFS, make([]byte, imageSize), 0o600)).To(Succeed()) | ||
| return rootFS | ||
| } | ||
|
|
||
| type fakeHost struct { | ||
| volumeDir string | ||
| } | ||
|
|
||
| func (h fakeHost) PluginDir(string) string { return h.volumeDir } | ||
| func (h fakeHost) MachinePluginDir(string, string) string { return h.volumeDir } | ||
| func (h fakeHost) MachineVolumeDir(string, string, string) string { return h.volumeDir } | ||
|
|
||
| type fakeImageCache struct { | ||
| rootFSPath string | ||
| } | ||
|
|
||
| func (c fakeImageCache) Get(context.Context, string) (*ociutils.Image, error) { | ||
| return &ociutils.Image{RootFS: &ociutils.FileLayer{Path: c.rootFSPath}}, nil | ||
| } | ||
|
|
||
| func (fakeImageCache) AddListener(ociutils.Listener) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,24 +17,60 @@ type Exec struct{} | |
|
|
||
| const filePerm = 0660 | ||
|
|
||
| func (Exec) Create(filename string, opts ...CreateOption) error { | ||
| // Create writes a raw disk image at filename. | ||
| // A source file, if given, is copied and then extended to the requested size. | ||
| // It returns an error if the requested size is smaller than the source. | ||
| func (Exec) Create(filename string, opts ...CreateOption) (err error) { | ||
| o := &CreateOptions{} | ||
| o.ApplyOptions(opts) | ||
| log := ctrl.Log.WithName("raw-disk").WithValues("filename", filename) | ||
|
|
||
| if o.SourceFile == "" { | ||
| if o.Size == nil { | ||
| return fmt.Errorf("must specify Size when creating without source file") | ||
| if o.Size != nil && *o.Size <= 0 { | ||
| return fmt.Errorf("size must be greater than zero, got %d", *o.Size) | ||
| } | ||
|
|
||
| if o.SourceFile == "" && o.Size == nil { | ||
| return fmt.Errorf("must specify Size when creating without source file") | ||
| } | ||
|
|
||
| var wantSize int64 | ||
| if o.Size != nil { | ||
| wantSize = *o.Size | ||
| } | ||
|
|
||
| if o.SourceFile != "" && wantSize > 0 { | ||
| fi, err := os.Stat(o.SourceFile) | ||
| if err != nil { | ||
| return fmt.Errorf("could not stat %q: %w", o.SourceFile, err) | ||
| } | ||
| if fi.Size() > wantSize { | ||
| return fmt.Errorf("cannot create %q at %d: source file %q is already %d", filename, wantSize, o.SourceFile, fi.Size()) | ||
| } | ||
| } | ||
|
|
||
| defer func() { | ||
| if err != nil { | ||
| os.Remove(filename) | ||
| } | ||
| seek := *o.Size | ||
| }() | ||
|
Comment on lines
+51
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Do not remove a destination that this call did not create.
Create the disk at an exclusive or temporary path, and remove only a path owned by this invocation. 🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed with a test, the suggested solution with the temp path is nonsense. On it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You are interacting with an AI system. |
||
|
|
||
| if o.SourceFile == "" { | ||
| // Position the file cursor one byte before the desired seek position to write a single byte, | ||
| // to ensure that data is written at the exact byte position specified by seek. | ||
| if err := createEmptyFileWithSeek(log, filename, seek-1); err != nil { | ||
| if err := createEmptyFileWithSeek(log, filename, wantSize-1); err != nil { | ||
| return fmt.Errorf("failed creating the empty ephemeral disk at %s: %w", filename, err) | ||
| } | ||
| } else { | ||
| if err := copyFile(log, o.SourceFile, filename); err != nil { | ||
| return fmt.Errorf("failed creating virtual disk image, source: %s, destination: %s: %w", o.SourceFile, filename, err) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| if err := copyFile(log, o.SourceFile, filename); err != nil { | ||
| return fmt.Errorf("failed creating virtual disk image, source: %s, destination: %s: %w", o.SourceFile, filename, err) | ||
| } | ||
|
|
||
| if wantSize > 0 { | ||
| if err := os.Truncate(filename, wantSize); err != nil { | ||
| return fmt.Errorf("resizing file: %w", err) | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.