diff options
Diffstat (limited to 'pkg/machine')
-rw-r--r-- | pkg/machine/fcos.go | 40 | ||||
-rw-r--r-- | pkg/machine/fcos_arm64.go | 16 | ||||
-rw-r--r-- | pkg/machine/pull.go | 115 | ||||
-rw-r--r-- | pkg/machine/qemu/machine.go | 82 | ||||
-rw-r--r-- | pkg/machine/qemu/options_linux_arm64.go | 41 |
5 files changed, 236 insertions, 58 deletions
diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 0c6a2485e..32f943c87 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -2,17 +2,13 @@ package machine import ( "crypto/sha256" - "io" "io/ioutil" url2 "net/url" - "os" "path/filepath" "runtime" "strings" - "github.com/containers/storage/pkg/archive" digest "github.com/opencontainers/go-digest" - "github.com/sirupsen/logrus" ) // These should eventually be moved into machine/qemu as @@ -75,41 +71,7 @@ func (f FcosDownload) DownloadImage() error { return err } } - uncompressedFileWriter, err := os.OpenFile(f.getLocalUncompressedName(), os.O_CREATE|os.O_RDWR, 0600) - if err != nil { - return err - } - sourceFile, err := ioutil.ReadFile(f.LocalPath) - if err != nil { - return err - } - compressionType := archive.DetectCompression(sourceFile) - f.CompressionType = compressionType.Extension() - - switch f.CompressionType { - case "tar.xz": - return decompressXZ(f.LocalPath, uncompressedFileWriter) - default: - // File seems to be uncompressed, make a copy - if err := copyFile(f.LocalPath, uncompressedFileWriter); err != nil { - return err - } - } - return nil -} - -func copyFile(src string, dest *os.File) error { - source, err := os.Open(src) - if err != nil { - return err - } - defer func() { - if err := source.Close(); err != nil { - logrus.Error(err) - } - }() - _, err = io.Copy(dest, source) - return err + return Decompress(f.LocalPath, f.getLocalUncompressedName()) } func (f FcosDownload) Get() *Download { diff --git a/pkg/machine/fcos_arm64.go b/pkg/machine/fcos_arm64.go index ab50ca874..f5cd5a505 100644 --- a/pkg/machine/fcos_arm64.go +++ b/pkg/machine/fcos_arm64.go @@ -2,9 +2,9 @@ package machine import ( "encoding/json" - "fmt" "io/ioutil" "net/http" + url2 "net/url" "github.com/sirupsen/logrus" ) @@ -14,9 +14,7 @@ const aarchBaseURL = "https://fedorapeople.org/groups/fcos-images/builds/latest/ // Total hack until automation is possible. // We need a proper json file at least to automate func getFCOSDownload() (*fcosDownloadInfo, error) { - meta := Build{} - fmt.Println(aarchBaseURL + "meta.json") resp, err := http.Get(aarchBaseURL + "meta.json") if err != nil { return nil, err @@ -33,8 +31,18 @@ func getFCOSDownload() (*fcosDownloadInfo, error) { if err := json.Unmarshal(body, &meta); err != nil { return nil, err } + pathURL, err := url2.Parse(meta.BuildArtifacts.Qemu.Path) + if err != nil { + return nil, err + } + + baseURL, err := url2.Parse(aarchBaseURL) + if err != nil { + return nil, err + } + pullURL := baseURL.ResolveReference(pathURL) return &fcosDownloadInfo{ - Location: "https://fedorapeople.org/groups/fcos-images/builds/latest/aarch64/fedora-coreos-33.20210310.dev.0-qemu.aarch64.qcow2", + Location: pullURL.String(), Release: "", Sha256Sum: meta.BuildArtifacts.Qemu.Sha256, }, nil diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go index 39dde15b8..41abe6993 100644 --- a/pkg/machine/pull.go +++ b/pkg/machine/pull.go @@ -3,17 +3,94 @@ package machine import ( "fmt" "io" + "io/ioutil" "net/http" + url2 "net/url" "os" "os/exec" + "path/filepath" "strings" "time" + "github.com/containers/image/v5/pkg/compression" + "github.com/docker/docker/pkg/archive" "github.com/sirupsen/logrus" "github.com/vbauerster/mpb/v6" "github.com/vbauerster/mpb/v6/decor" ) +// GenericDownload is used when a user provides a URL +// or path for an image +type GenericDownload struct { + Download +} + +// NewGenericDownloader is used when the disk image is provided by the user +func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload, error) { + var ( + imageName string + ) + dataDir, err := GetDataDir(vmType) + if err != nil { + return nil, err + } + dl := Download{} + // Is pullpath a file or url? + getURL, err := url2.Parse(pullPath) + if err != nil { + return nil, err + } + if len(getURL.Scheme) > 0 { + urlSplit := strings.Split(pullPath, "/") + imageName = urlSplit[len(urlSplit)-1] + dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) + dl.URL = getURL + dl.LocalPath = filepath.Join(dataDir, imageName) + } else { + // Dealing with FilePath + imageName = filepath.Base(pullPath) + dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) + dl.LocalPath = pullPath + } + dl.VMName = vmName + dl.ImageName = imageName + // The download needs to be pulled into the datadir + + gd := GenericDownload{Download: dl} + gd.LocalUncompressedFile = gd.getLocalUncompressedName() + return gd, nil +} + +func (g GenericDownload) getLocalUncompressedName() string { + var ( + extension string + ) + switch { + case strings.HasSuffix(g.LocalPath, ".bz2"): + extension = ".bz2" + case strings.HasSuffix(g.LocalPath, ".gz"): + extension = ".gz" + case strings.HasSuffix(g.LocalPath, ".xz"): + extension = ".xz" + } + uncompressedFilename := filepath.Join(filepath.Dir(g.LocalUncompressedFile), g.VMName+"_"+g.ImageName) + return strings.TrimSuffix(uncompressedFilename, extension) +} + +func (g GenericDownload) DownloadImage() error { + // If we have a URL for this "downloader", we now pull it + if g.URL != nil { + if err := DownloadVMImage(g.URL, g.LocalPath); err != nil { + return err + } + } + return Decompress(g.LocalPath, g.getLocalUncompressedName()) +} + +func (g GenericDownload) Get() *Download { + return &g.Download +} + // DownloadVMImage downloads a VM image from url to given path // with download status func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error { @@ -38,7 +115,7 @@ func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error { }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("error downloading VM image: %s", resp.Status) + return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status) } size := resp.ContentLength urlSplit := strings.Split(downloadURL.String(), "/") @@ -75,6 +152,22 @@ func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error { return nil } +func Decompress(localPath, uncompressedPath string) error { + uncompressedFileWriter, err := os.OpenFile(uncompressedPath, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return err + } + sourceFile, err := ioutil.ReadFile(localPath) + if err != nil { + return err + } + + if compressionType := archive.DetectCompression(sourceFile); compressionType.Extension() == "tar.xz" { + return decompressXZ(localPath, uncompressedFileWriter) + } + return decompressEverythingElse(localPath, uncompressedFileWriter) +} + // Will error out if file without .xz already exists // Maybe extracting then renameing is a good idea here.. // depends on xz: not pre-installed on mac, so it becomes a brew dependecy @@ -95,3 +188,23 @@ func decompressXZ(src string, output io.Writer) error { }() return cmd.Run() } + +func decompressEverythingElse(src string, output io.Writer) error { + fmt.Println("Extracting compressed file") + f, err := os.Open(src) + if err != nil { + return err + } + uncompressStream, _, err := compression.AutoDecompress(f) + if err != nil { + return err + } + defer func() { + if err := uncompressStream.Close(); err != nil { + logrus.Error(err) + } + }() + + _, err = io.Copy(output, uncompressStream) + return err +} diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index fe155750f..fdb528a86 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -12,9 +12,8 @@ import ( "strconv" "time" - "github.com/containers/podman/v3/utils" - "github.com/containers/podman/v3/pkg/machine" + "github.com/containers/podman/v3/utils" "github.com/containers/storage/pkg/homedir" "github.com/digitalocean/go-qemu/qmp" "github.com/pkg/errors" @@ -83,7 +82,7 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) { return nil, err } vm.QMPMonitor = monitor - cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server,nowait"}...) + cmd = append(cmd, []string{"-qmp", monitor.Network + ":/" + monitor.Address + ",server=on,wait=off"}...) // Add network cmd = append(cmd, "-nic", "user,model=virtio,hostfwd=tcp::"+strconv.Itoa(vm.Port)+"-:22") @@ -96,7 +95,7 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) { // Add serial port for readiness cmd = append(cmd, []string{ "-device", "virtio-serial", - "-chardev", "socket,path=" + virtualSocketPath + ",server,nowait,id=" + vm.Name + "_ready", + "-chardev", "socket,path=" + virtualSocketPath + ",server=on,wait=off,id=" + vm.Name + "_ready", "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...) vm.CmdLine = cmd return vm, nil @@ -135,15 +134,29 @@ func (v *MachineVM) Init(opts machine.InitOptions) error { jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json" v.IdentityPath = filepath.Join(sshDir, v.Name) - dd, err := machine.NewFcosDownloader(vmtype, v.Name) - if err != nil { - return err + // The user has provided an alternate image which can be a file path + // or URL. + if len(opts.ImagePath) > 0 { + g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) + if err != nil { + return err + } + v.ImagePath = g.Get().LocalUncompressedFile + if err := g.DownloadImage(); err != nil { + return err + } + } else { + // Get the image as usual + dd, err := machine.NewFcosDownloader(vmtype, v.Name) + if err != nil { + return err + } + v.ImagePath = dd.Get().LocalUncompressedFile + if err := dd.DownloadImage(); err != nil { + return err + } } - v.ImagePath = dd.Get().LocalUncompressedFile - if err := dd.DownloadImage(); err != nil { - return err - } // Add arch specific options including image location v.CmdLine = append(v.CmdLine, v.addArchOptions()...) @@ -171,10 +184,20 @@ func (v *MachineVM) Init(opts machine.InitOptions) error { return err } + originalDiskSize, err := getDiskSize(v.ImagePath) + if err != nil { + return err + } // Resize the disk image to input disk size - resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) - if err := resize.Run(); err != nil { - return errors.Errorf("error resizing image: %q", err) + // only if the virtualdisk size is less than + // the given disk size + if opts.DiskSize<<(10*3) > originalDiskSize { + resize := exec.Command("qemu-img", []string{"resize", v.ImagePath, strconv.Itoa(int(opts.DiskSize)) + "G"}...) + resize.Stdout = os.Stdout + resize.Stderr = os.Stderr + if err := resize.Run(); err != nil { + return errors.Errorf("error resizing image: %q", err) + } } // Write the ignition file ign := machine.DynamicIgnition{ @@ -372,3 +395,34 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error { return cmd.Run() } + +// executes qemu-image info to get the virtual disk size +// of the diskimage +func getDiskSize(path string) (uint64, error) { + diskInfo := exec.Command("qemu-img", "info", "--output", "json", path) + stdout, err := diskInfo.StdoutPipe() + if err != nil { + return 0, err + } + if err := diskInfo.Start(); err != nil { + return 0, err + } + tmpInfo := struct { + VirtualSize uint64 `json:"virtual-size"` + Filename string `json:"filename"` + ClusterSize int64 `json:"cluster-size"` + Format string `json:"format"` + FormatSpecific struct { + Type string `json:"type"` + Data map[string]string `json:"data"` + } + DirtyFlag bool `json:"dirty-flag"` + }{} + if err := json.NewDecoder(stdout).Decode(&tmpInfo); err != nil { + return 0, err + } + if err := diskInfo.Wait(); err != nil { + return 0, err + } + return tmpInfo.VirtualSize, nil +} diff --git a/pkg/machine/qemu/options_linux_arm64.go b/pkg/machine/qemu/options_linux_arm64.go new file mode 100644 index 000000000..948117653 --- /dev/null +++ b/pkg/machine/qemu/options_linux_arm64.go @@ -0,0 +1,41 @@ +package qemu + +import ( + "os" + "path/filepath" +) + +var ( + QemuCommand = "qemu-system-aarch64" +) + +func (v *MachineVM) addArchOptions() []string { + opts := []string{ + "-accel", "kvm", + "-cpu", "host", + "-M", "virt,gic-version=max", + "-bios", getQemuUefiFile("QEMU_EFI.fd"), + } + return opts +} + +func (v *MachineVM) prepare() error { + return nil +} + +func (v *MachineVM) archRemovalFiles() []string { + return []string{} +} + +func getQemuUefiFile(name string) string { + dirs := []string{ + "/usr/share/qemu-efi-aarch64", + "/usr/share/edk2/aarch64", + } + for _, dir := range dirs { + if _, err := os.Stat(dir); err == nil { + return filepath.Join(dir, name) + } + } + return name +} |