From b4ff0ea4bc62d58adb41b5bb1126ad0e1e0848f8 Mon Sep 17 00:00:00 2001 From: Richard Turc Date: Tue, 8 Sep 2020 20:32:08 +0200 Subject: [PATCH] [builder/qemu] Skip resize step when skip_resize_disk is enable #9860 (#9896) * [builder/qemu] Skip resize step when skip_resize_disk is enable #9860 * Update builder/qemu/builder_test.go Improve the code quality Co-authored-by: Wilken Rivera * Update files for unit tests Co-authored-by: Wilken Rivera --- builder/qemu/builder.go | 9 ++ builder/qemu/builder.hcl2spec.go | 2 + builder/qemu/builder_test.go | 15 ++++ builder/qemu/driver_mock.go | 64 +++++++++++++++ builder/qemu/step_resize_disk.go | 3 +- builder/qemu/step_resize_disk_test.go | 82 +++++++++++++++++++ builder/qemu/step_test.go | 19 +++++ .../builder/qemu/Config-not-required.mdx | 4 + 8 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 builder/qemu/driver_mock.go create mode 100644 builder/qemu/step_resize_disk_test.go create mode 100644 builder/qemu/step_test.go diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 23d6d29fe..ea2cc326c 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -130,6 +130,10 @@ type Config struct { // for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size // number is provided with no units, Packer will default to Megabytes. DiskSize string `mapstructure:"disk_size" required:"false"` + // Packer resizes the QCOW2 image using + // qemu-img resize. Set this option to true to disable resizing. + // Defaults to false. + SkipResizeDisk bool `mapstructure:"skip_resize_disk" required:"false"` // The cache mode to use for disk. Allowed values include any of // `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By // default, this is set to `writeback`. @@ -509,6 +513,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { errs, errors.New("disk_additional_size can only be used when disk_image is false")) } + if b.config.SkipResizeDisk && !(b.config.DiskImage) { + errs = packer.MultiErrorAppend( + errs, errors.New("skip_resize_disk can only be used when disk_image is true")) + } + if _, ok := accels[b.config.Accelerator]; !ok { errs = packer.MultiErrorAppend( errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed")) diff --git a/builder/qemu/builder.hcl2spec.go b/builder/qemu/builder.hcl2spec.go index 74cd2f6ca..af8da89a6 100644 --- a/builder/qemu/builder.hcl2spec.go +++ b/builder/qemu/builder.hcl2spec.go @@ -93,6 +93,7 @@ type FlatConfig struct { CpuCount *int `mapstructure:"cpus" required:"false" cty:"cpus" hcl:"cpus"` DiskInterface *string `mapstructure:"disk_interface" required:"false" cty:"disk_interface" hcl:"disk_interface"` DiskSize *string `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"` + SkipResizeDisk *bool `mapstructure:"skip_resize_disk" required:"false" cty:"skip_resize_disk" hcl:"skip_resize_disk"` DiskCache *string `mapstructure:"disk_cache" required:"false" cty:"disk_cache" hcl:"disk_cache"` DiskDiscard *string `mapstructure:"disk_discard" required:"false" cty:"disk_discard" hcl:"disk_discard"` DetectZeroes *string `mapstructure:"disk_detect_zeroes" required:"false" cty:"disk_detect_zeroes" hcl:"disk_detect_zeroes"` @@ -218,6 +219,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "cpus": &hcldec.AttrSpec{Name: "cpus", Type: cty.Number, Required: false}, "disk_interface": &hcldec.AttrSpec{Name: "disk_interface", Type: cty.String, Required: false}, "disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.String, Required: false}, + "skip_resize_disk": &hcldec.AttrSpec{Name: "skip_resize_disk", Type: cty.Bool, Required: false}, "disk_cache": &hcldec.AttrSpec{Name: "disk_cache", Type: cty.String, Required: false}, "disk_discard": &hcldec.AttrSpec{Name: "disk_discard", Type: cty.String, Required: false}, "disk_detect_zeroes": &hcldec.AttrSpec{Name: "disk_detect_zeroes", Type: cty.String, Required: false}, diff --git a/builder/qemu/builder_test.go b/builder/qemu/builder_test.go index 3eb243abb..79d661e21 100644 --- a/builder/qemu/builder_test.go +++ b/builder/qemu/builder_test.go @@ -302,6 +302,21 @@ func TestBuilderPrepare_UseBackingFile(t *testing.T) { } } +func TestBuilderPrepare_SkipResizeDisk(t *testing.T) { + config := testConfig() + config["skip_resize_disk"] = true + config["disk_image"] = false + + b := Builder{} + _, warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Errorf("unexpected warns when calling prepare with skip_resize_disk set to true: %#v", warns) + } + if err == nil { + t.Errorf("setting skip_resize_disk to true when disk_image is false should have error") + } +} + func TestBuilderPrepare_FloppyFiles(t *testing.T) { var b Builder config := testConfig() diff --git a/builder/qemu/driver_mock.go b/builder/qemu/driver_mock.go new file mode 100644 index 000000000..a01ff7d5a --- /dev/null +++ b/builder/qemu/driver_mock.go @@ -0,0 +1,64 @@ +package qemu + +import "sync" + +type DriverMock struct { + sync.Mutex + + StopCalled bool + StopErr error + + QemuCalls [][]string + QemuErrs []error + + WaitForShutdownCalled bool + WaitForShutdownState bool + + QemuImgCalls [][]string + QemuImgErrs []error + + VerifyCalled bool + VerifyErr error + + VersionCalled bool + VersionResult string + VersionErr error +} + +func (d *DriverMock) Stop() error { + d.StopCalled = true + return d.StopErr +} + +func (d *DriverMock) Qemu(args ...string) error { + d.QemuCalls = append(d.QemuCalls, args) + + if len(d.QemuErrs) >= len(d.QemuCalls) { + return d.QemuErrs[len(d.QemuCalls)-1] + } + return nil +} + +func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool { + d.WaitForShutdownCalled = true + return d.WaitForShutdownState +} + +func (d *DriverMock) QemuImg(args ...string) error { + d.QemuImgCalls = append(d.QemuImgCalls, args) + + if len(d.QemuImgErrs) >= len(d.QemuImgCalls) { + return d.QemuImgErrs[len(d.QemuImgCalls)-1] + } + return nil +} + +func (d *DriverMock) Verify() error { + d.VerifyCalled = true + return d.VerifyErr +} + +func (d *DriverMock) Version() (string, error) { + d.VersionCalled = true + return d.VersionResult, d.VersionErr +} diff --git a/builder/qemu/step_resize_disk.go b/builder/qemu/step_resize_disk.go index 74949281b..d79768214 100644 --- a/builder/qemu/step_resize_disk.go +++ b/builder/qemu/step_resize_disk.go @@ -25,7 +25,8 @@ func (s *stepResizeDisk) Run(ctx context.Context, state multistep.StateBag) mult path, config.DiskSize, } - if config.DiskImage == false { + + if config.DiskImage == false || config.SkipResizeDisk == true { return multistep.ActionContinue } diff --git a/builder/qemu/step_resize_disk_test.go b/builder/qemu/step_resize_disk_test.go new file mode 100644 index 000000000..a4335d54e --- /dev/null +++ b/builder/qemu/step_resize_disk_test.go @@ -0,0 +1,82 @@ +package qemu + +import ( + "context" + "testing" + + "github.com/hashicorp/packer/helper/multistep" +) + +func TestStepResizeDisk_Run(t *testing.T) { + state := testState(t) + driver := state.Get("driver").(*DriverMock) + + config := &Config{ + DiskImage: true, + SkipResizeDisk: false, + DiskSize: "4096M", + Format: "qcow2", + OutputDir: "/test/", + VMName: "test", + } + state.Put("config", config) + step := new(stepResizeDisk) + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if len(driver.QemuImgCalls) == 0 { + t.Fatal("should qemu-img called") + } + if len(driver.QemuImgCalls[0]) != 5 { + t.Fatal("should 5 qemu-img parameters") + } +} + +func TestStepResizeDisk_SkipIso(t *testing.T) { + state := testState(t) + driver := state.Get("driver").(*DriverMock) + config := &Config{ + DiskImage: false, + SkipResizeDisk: false, + } + state.Put("config", config) + step := new(stepResizeDisk) + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if len(driver.QemuImgCalls) > 0 { + t.Fatal("should NOT qemu-img called") + } +} + +func TestStepResizeDisk_SkipOption(t *testing.T) { + state := testState(t) + driver := state.Get("driver").(*DriverMock) + config := &Config{ + DiskImage: false, + SkipResizeDisk: true, + } + state.Put("config", config) + step := new(stepResizeDisk) + + // Test the run + if action := step.Run(context.Background(), state); action != multistep.ActionContinue { + t.Fatalf("bad action: %#v", action) + } + if _, ok := state.GetOk("error"); ok { + t.Fatal("should NOT have error") + } + if len(driver.QemuImgCalls) > 0 { + t.Fatal("should NOT qemu-img called") + } +} diff --git a/builder/qemu/step_test.go b/builder/qemu/step_test.go new file mode 100644 index 000000000..4a782d1d4 --- /dev/null +++ b/builder/qemu/step_test.go @@ -0,0 +1,19 @@ +package qemu + +import ( + "bytes" + "testing" + + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/packer" +) + +func testState(t *testing.T) multistep.StateBag { + state := new(multistep.BasicStateBag) + state.Put("driver", new(DriverMock)) + state.Put("ui", &packer.BasicUi{ + Reader: new(bytes.Buffer), + Writer: new(bytes.Buffer), + }) + return state +} diff --git a/website/pages/partials/builder/qemu/Config-not-required.mdx b/website/pages/partials/builder/qemu/Config-not-required.mdx index 892b8e312..0e1eee693 100644 --- a/website/pages/partials/builder/qemu/Config-not-required.mdx +++ b/website/pages/partials/builder/qemu/Config-not-required.mdx @@ -53,6 +53,10 @@ for disk_size, Packer uses a default of `40960M` (40 GB). If a disk_size number is provided with no units, Packer will default to Megabytes. +- `skip_resize_disk` (bool) - Packer resizes the QCOW2 image using + qemu-img resize. Set this option to true to disable resizing. + Defaults to false. + - `disk_cache` (string) - The cache mode to use for disk. Allowed values include any of `writethrough`, `writeback`, `none`, `unsafe` or `directsync`. By default, this is set to `writeback`.