Merge pull request #9949 from hashicorp/do_6963

Skip qemu-img convert on OSX
This commit is contained in:
Megan Marsh 2020-09-17 11:31:12 -07:00 committed by GitHub
commit 57413132b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 194 additions and 13 deletions

View File

@ -69,7 +69,13 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Label: b.config.CDConfig.CDLabel, Label: b.config.CDConfig.CDLabel,
}, },
new(stepCreateDisk), new(stepCreateDisk),
new(stepCopyDisk), &stepCopyDisk{
DiskImage: b.config.DiskImage,
Format: b.config.Format,
OutputDir: b.config.OutputDir,
UseBackingFile: b.config.UseBackingFile,
VMName: b.config.VMName,
},
new(stepResizeDisk), new(stepResizeDisk),
new(stepHTTPIPDiscover), new(stepHTTPIPDiscover),
&common.StepHTTPServer{ &common.StepHTTPServer{

View File

@ -143,7 +143,12 @@ type Config struct {
// using qemu-img convert. Defaults to false. // using qemu-img convert. Defaults to false.
DiskCompression bool `mapstructure:"disk_compression" required:"false"` DiskCompression bool `mapstructure:"disk_compression" required:"false"`
// Either `qcow2` or `raw`, this specifies the output format of the virtual // Either `qcow2` or `raw`, this specifies the output format of the virtual
// machine image. This defaults to `qcow2`. // machine image. This defaults to `qcow2`. Due to a long-standing bug with
// `qemu-img convert` on OSX, sometimes the qemu-img convert call will
// create a corrupted image. If this is an issue for you, make sure that the
// the output format matches the input file's format, and Packer will
// perform a simple copy operation instead. See
// https://bugs.launchpad.net/qemu/+bug/1776920 for more details.
Format string `mapstructure:"format" required:"false"` Format string `mapstructure:"format" required:"false"`
// Packer defaults to building QEMU virtual machines by // Packer defaults to building QEMU virtual machines by
// launching a GUI that shows the console of the machine being built. When this // launching a GUI that shows the console of the machine being built. When this

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"os"
"os/exec" "os/exec"
"regexp" "regexp"
"strings" "strings"
@ -22,6 +23,10 @@ type DriverCancelCallback func(state multistep.StateBag) bool
// A driver is able to talk to qemu-system-x86_64 and perform certain // A driver is able to talk to qemu-system-x86_64 and perform certain
// operations with it. // operations with it.
type Driver interface { type Driver interface {
// Copy bypasses qemu-img convert and directly copies an image
// that doesn't need converting.
Copy(string, string) error
// Stop stops a running machine, forcefully. // Stop stops a running machine, forcefully.
Stop() error Stop() error
@ -65,6 +70,33 @@ func (d *QemuDriver) Stop() error {
return nil return nil
} }
func (d *QemuDriver) Copy(sourceName, targetName string) error {
source, err := os.Open(sourceName)
if err != nil {
err = fmt.Errorf("Error opening iso for copy: %s", err)
return err
}
defer source.Close()
// Create will truncate an existing file
target, err := os.Create(targetName)
if err != nil {
err = fmt.Errorf("Error creating hard drive in output dir: %s", err)
return err
}
defer target.Close()
log.Printf("Copying %s to %s", source.Name(), target.Name())
bytes, err := io.Copy(target, source)
if err != nil {
err = fmt.Errorf("Error copying iso to output dir: %s", err)
return err
}
log.Printf(fmt.Sprintf("Copied %d bytes", bytes))
return nil
}
func (d *QemuDriver) Qemu(qemuArgs ...string) error { func (d *QemuDriver) Qemu(qemuArgs ...string) error {
d.lock.Lock() d.lock.Lock()
defer d.lock.Unlock() defer d.lock.Unlock()

View File

@ -5,6 +5,9 @@ import "sync"
type DriverMock struct { type DriverMock struct {
sync.Mutex sync.Mutex
CopyCalled bool
CopyErr error
StopCalled bool StopCalled bool
StopErr error StopErr error
@ -14,8 +17,9 @@ type DriverMock struct {
WaitForShutdownCalled bool WaitForShutdownCalled bool
WaitForShutdownState bool WaitForShutdownState bool
QemuImgCalls [][]string QemuImgCalled bool
QemuImgErrs []error QemuImgCalls [][]string
QemuImgErrs []error
VerifyCalled bool VerifyCalled bool
VerifyErr error VerifyErr error
@ -25,6 +29,11 @@ type DriverMock struct {
VersionErr error VersionErr error
} }
func (d *DriverMock) Copy(source, dst string) error {
d.CopyCalled = true
return d.CopyErr
}
func (d *DriverMock) Stop() error { func (d *DriverMock) Stop() error {
d.StopCalled = true d.StopCalled = true
return d.StopErr return d.StopErr
@ -45,6 +54,7 @@ func (d *DriverMock) WaitForShutdown(cancelCh <-chan struct{}) bool {
} }
func (d *DriverMock) QemuImg(args ...string) error { func (d *DriverMock) QemuImg(args ...string) error {
d.QemuImgCalled = true
d.QemuImgCalls = append(d.QemuImgCalls, args) d.QemuImgCalls = append(d.QemuImgCalls, args)
if len(d.QemuImgErrs) >= len(d.QemuImgCalls) { if len(d.QemuImgErrs) >= len(d.QemuImgCalls) {

View File

@ -11,26 +11,45 @@ import (
// This step copies the virtual disk that will be used as the // This step copies the virtual disk that will be used as the
// hard drive for the virtual machine. // hard drive for the virtual machine.
type stepCopyDisk struct{} type stepCopyDisk struct {
DiskImage bool
Format string
OutputDir string
UseBackingFile bool
VMName string
}
func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { func (s *stepCopyDisk) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
config := state.Get("config").(*Config)
driver := state.Get("driver").(Driver) driver := state.Get("driver").(Driver)
isoPath := state.Get("iso_path").(string) isoPath := state.Get("iso_path").(string)
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
path := filepath.Join(config.OutputDir, config.VMName) path := filepath.Join(s.OutputDir, s.VMName)
if !s.DiskImage || s.UseBackingFile {
return multistep.ActionContinue
}
// isoPath extention is:
ext := filepath.Ext(isoPath)
if ext[1:] == s.Format {
ui.Message("File extension already matches desired output format. " +
"Skipping qemu-img convert step")
err := driver.Copy(isoPath, path)
if err != nil {
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
command := []string{ command := []string{
"convert", "convert",
"-O", config.Format, "-O", s.Format,
isoPath, isoPath,
path, path,
} }
if !config.DiskImage || config.UseBackingFile {
return multistep.ActionContinue
}
ui.Say("Copying hard drive...") ui.Say("Copying hard drive...")
if err := driver.QemuImg(command...); err != nil { if err := driver.QemuImg(command...); err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err) err := fmt.Errorf("Error creating hard drive: %s", err)

View File

@ -0,0 +1,91 @@
package qemu
import (
"context"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func copyTestState(t *testing.T, d *DriverMock) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", packer.TestUi(t))
state.Put("driver", d)
state.Put("iso_path", "example_source.qcow2")
return state
}
func Test_StepCopySkip(t *testing.T) {
testcases := []stepCopyDisk{
stepCopyDisk{
DiskImage: false,
UseBackingFile: false,
},
stepCopyDisk{
DiskImage: true,
UseBackingFile: true,
},
stepCopyDisk{
DiskImage: false,
UseBackingFile: true,
},
}
for _, tc := range testcases {
d := new(DriverMock)
state := copyTestState(t, d)
action := tc.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled || d.QemuImgCalled {
t.Fatalf("Should have skipped step since DiskImage and UseBackingFile are not set")
}
}
}
func Test_StepCopyCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "qcow2",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if !d.CopyCalled {
t.Fatalf("Should have copied since all extensions are qcow2")
}
if d.QemuImgCalled {
t.Fatalf("Should not have called qemu-img when formats match")
}
}
func Test_StepQemuImgCalled(t *testing.T) {
step := stepCopyDisk{
DiskImage: true,
Format: "raw",
VMName: "output.qcow2",
}
d := new(DriverMock)
state := copyTestState(t, d)
action := step.Run(context.TODO(), state)
if action != multistep.ActionContinue {
t.Fatalf("Should have gotten an ActionContinue")
}
if d.CopyCalled {
t.Fatalf("Should not have copied since extensions don't match")
}
if !d.QemuImgCalled {
t.Fatalf("Should have called qemu-img since extensions don't match")
}
}

View File

@ -196,6 +196,8 @@ necessary for this build to succeed and can be found further down the page.
### Troubleshooting ### Troubleshooting
#### Invalid Keymaps
Some users have experienced errors complaining about invalid keymaps. This Some users have experienced errors complaining about invalid keymaps. This
seems to be related to having a `common` directory or file in the directory seems to be related to having a `common` directory or file in the directory
they've run Packer in, like the packer source directory. This appears to be an they've run Packer in, like the packer source directory. This appears to be an
@ -205,3 +207,14 @@ file/directory or run in another directory.
Some users have reported issues with incorrect keymaps using qemu version 2.11. Some users have reported issues with incorrect keymaps using qemu version 2.11.
This is a bug with qemu, and the solution is to upgrade, or downgrade to 2.10.1 This is a bug with qemu, and the solution is to upgrade, or downgrade to 2.10.1
or earlier. or earlier.
#### Corrupted image after Packer calls qemu-img convert on OSX
Due to an upstream bug with `qemu-img convert` on OSX, sometimes the qemu-img
convert call will create a corrupted image. If this is an issue for you, make
sure that the the output format (provided using the option `format`) matches
the input file's format and file extension, and Packer will
perform a simple copy operation instead. You will also want to set
`"skip_compaction": true,` and `"disk_compression": false` to skip a final
image conversion at the end of the build. See
https://bugs.launchpad.net/qemu/+bug/1776920 for more details.

View File

@ -77,7 +77,12 @@
using qemu-img convert. Defaults to false. using qemu-img convert. Defaults to false.
- `format` (string) - Either `qcow2` or `raw`, this specifies the output format of the virtual - `format` (string) - Either `qcow2` or `raw`, this specifies the output format of the virtual
machine image. This defaults to `qcow2`. machine image. This defaults to `qcow2`. Due to a long-standing bug with
`qemu-img convert` on OSX, sometimes the qemu-img convert call will
create a corrupted image. If this is an issue for you, make sure that the
the output format matches the input file's format, and Packer will
perform a simple copy operation instead. See
https://bugs.launchpad.net/qemu/+bug/1776920 for more details.
- `headless` (bool) - Packer defaults to building QEMU virtual machines by - `headless` (bool) - Packer defaults to building QEMU virtual machines by
launching a GUI that shows the console of the machine being built. When this launching a GUI that shows the console of the machine being built. When this