machine refactoring preparations for hyperv

before we can support hyperv as a virtualization option for podman
machine, several areas in machine will require cleanup.  this is the
first pass of these changes to keep the review burden low.  changes
include:

  * convert artifact, format (image format) and compression to enums
    with string methods
  * rename Provider interface to VirtProvider
  * change Provider implementation in QEMU to QEMUVirt
  * change Provider implementation in WSL to WSLVirt

as mentioned earlier, there will be several more of these refactoring
PRs because assumptions were made about associations of platforms and
virt providers as well as compression and image formats.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude 2023-02-20 10:40:13 -06:00
parent a9ec6492e8
commit ebb45b5bdd
12 changed files with 250 additions and 59 deletions

View file

@ -8,6 +8,6 @@ import (
"github.com/containers/podman/v4/pkg/machine/qemu"
)
func GetSystemDefaultProvider() machine.Provider {
func GetSystemDefaultProvider() machine.VirtProvider {
return qemu.GetVirtualizationProvider()
}

View file

@ -5,6 +5,6 @@ import (
"github.com/containers/podman/v4/pkg/machine/wsl"
)
func GetSystemDefaultProvider() machine.Provider {
func GetSystemDefaultProvider() machine.VirtProvider {
return wsl.GetWSLProvider()
}

View file

@ -17,7 +17,7 @@ var (
vmtype = "apple"
)
func GetVirtualizationProvider() machine.Provider {
func GetVirtualizationProvider() machine.VirtProvider {
return hvProvider
}

View file

@ -47,7 +47,7 @@ const (
DefaultMachineName string = "podman-machine-default"
)
type Provider interface {
type VirtProvider interface {
NewMachine(opts InitOptions) (VM, error)
LoadVMByName(name string) (VM, error)
List(opts ListOptions) ([]*ListResponse, error)
@ -72,10 +72,10 @@ var (
type Download struct {
Arch string
Artifact string
Artifact artifact
CompressionType string
CacheDir string
Format string
Format imageFormat
ImageName string
LocalPath string
LocalUncompressedFile string

View file

@ -43,7 +43,7 @@ func TestMachine(t *testing.T) {
}
var _ = BeforeSuite(func() {
fcd, err := machine.GetFCOSDownload(defaultStream)
fcd, err := machine.GetFCOSDownload(defaultStream, machine.Xz)
if err != nil {
Fail("unable to get virtual machine image")
}

View file

@ -18,17 +18,13 @@ import (
"github.com/coreos/stream-metadata-go/fedoracoreos"
"github.com/coreos/stream-metadata-go/release"
"github.com/coreos/stream-metadata-go/stream"
digest "github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
// These should eventually be moved into machine/qemu as
// they are specific to running qemu
var (
artifact = "qemu"
Format = "qcow2.xz"
)
type imageCompression int64
type artifact int64
type imageFormat int64
const (
// Used for testing the latest podman in fcos
@ -36,14 +32,76 @@ const (
podmanTesting = "podman-testing"
PodmanTestingHost = "fedorapeople.org"
PodmanTestingURL = "groups/podman/testing"
Xz imageCompression = iota
Zip
Gz
Bz2
Qemu artifact = iota
HyperV
None
qcow imageFormat = iota
vhdx
Tar
)
//
// TODO artifact, imageformat, and imagecompression should be probably combined into some sort
// of object which can "produce" the correct output we are looking for bc things like
// image format contain both the image type AND the compression. This work can be done before
// or after the hyperv work. For now, my preference is to NOT change things and just get things
// typed strongly
//
func (a artifact) String() string {
if a == HyperV {
return "hyperv"
}
return "qemu"
}
func (imf imageFormat) String() string {
switch imf {
case vhdx:
return "vhdx.zip"
case Tar:
return "tar.xz"
}
return "qcow2.xz"
}
func (c imageCompression) String() string {
switch c {
case Gz:
return "gz"
case Zip:
return "zip"
case Bz2:
return "bz2"
}
return "xz"
}
func compressionFromFile(path string) imageCompression {
switch {
case strings.HasSuffix(path, Bz2.String()):
return Bz2
case strings.HasSuffix(path, Gz.String()):
return Gz
case strings.HasSuffix(path, Zip.String()):
return Zip
}
return Xz
}
type FcosDownload struct {
Download
}
func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload, error) {
info, err := GetFCOSDownload(imageStream)
info, err := GetFCOSDownload(imageStream, Xz)
if err != nil {
return nil, err
}
@ -62,9 +120,9 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload
fcd := FcosDownload{
Download: Download{
Arch: GetFcosArch(),
Artifact: artifact,
Artifact: Qemu,
CacheDir: cacheDir,
Format: Format,
Format: qcow,
ImageName: imageName,
LocalPath: filepath.Join(cacheDir, imageName),
Sha256sum: info.Sha256Sum,
@ -151,7 +209,7 @@ func getStreamURL(streamType string) url2.URL {
// This should get Exported and stay put as it will apply to all fcos downloads
// getFCOS parses fedoraCoreOS's stream and returns the image download URL and the release version
func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
func GetFCOSDownload(imageStream string, compression imageCompression) (*FcosDownloadInfo, error) {
var (
fcosstable stream.Stream
altMeta release.Release
@ -193,7 +251,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
if !ok {
return nil, fmt.Errorf("unable to pull VM image: no targetArch in stream")
}
qcow2, ok := arches.Media.Qemu.Artifacts["qcow2.xz"]
qcow2, ok := arches.Media.Qemu.Artifacts[qcow.String()]
if !ok {
return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
}
@ -202,7 +260,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
return &FcosDownloadInfo{
Location: disk.Location,
Sha256Sum: disk.Sha256,
CompressionType: "xz",
CompressionType: compression.String(),
}, nil
}
@ -217,7 +275,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
if artifacts == nil {
return nil, fmt.Errorf("unable to pull VM image: no artifact in stream")
}
qemu, ok := artifacts[artifact]
qemu, ok := artifacts[Qemu.String()]
if !ok {
return nil, fmt.Errorf("unable to pull VM image: no qemu artifact in stream")
}
@ -225,7 +283,7 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
if formats == nil {
return nil, fmt.Errorf("unable to pull VM image: no formats in stream")
}
qcow, ok := formats[Format]
qcow, ok := formats[qcow.String()]
if !ok {
return nil, fmt.Errorf("unable to pull VM image: no qcow2.xz format in stream")
}
@ -237,6 +295,6 @@ func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) {
Location: disk.Location,
Release: qemu.Release,
Sha256Sum: disk.Sha256,
CompressionType: "xz",
CompressionType: compression.String(),
}, nil
}

143
pkg/machine/fcos_test.go Normal file
View file

@ -0,0 +1,143 @@
package machine
import "testing"
func Test_compressionFromFile(t *testing.T) {
type args struct {
path string
}
var tests = []struct {
name string
args args
want imageCompression
}{
{
name: "xz",
args: args{
path: "/tmp/foo.xz",
},
want: Xz,
},
{
name: "gzip",
args: args{
path: "/tmp/foo.gz",
},
want: Gz,
},
{
name: "bz2",
args: args{
path: "/tmp/foo.bz2",
},
want: Bz2,
},
{
name: "default is xz",
args: args{
path: "/tmp/foo",
},
want: Xz,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := compressionFromFile(tt.args.path); got != tt.want {
t.Errorf("compressionFromFile() = %v, want %v", got, tt.want)
}
})
}
}
func TestImageCompression_String(t *testing.T) {
tests := []struct {
name string
c imageCompression
want string
}{
{
name: "xz",
c: Xz,
want: "xz",
},
{
name: "gz",
c: Gz,
want: "gz",
},
{
name: "bz2",
c: Bz2,
want: "bz2",
},
{
name: "zip",
c: Zip,
want: "zip",
},
{
name: "xz is default",
c: 99,
want: "xz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.c.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
func TestImageFormat_String(t *testing.T) {
tests := []struct {
name string
imf imageFormat
want string
}{
{
name: "vhdx.zip",
imf: vhdx,
want: "vhdx.zip",
},
{
name: "qcow2",
imf: qcow,
want: "qcow2.xz",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.imf.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
func Test_artifact_String(t *testing.T) {
tests := []struct {
name string
a artifact
want string
}{
{
name: "qemu",
a: Qemu,
want: "qemu",
},
{
name: "hyperv",
a: HyperV,
want: "hyperv",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.a.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -86,19 +86,9 @@ func supportedURL(path string) (url *url2.URL) {
}
func (d Download) GetLocalUncompressedFile(dataDir string) string {
var (
extension string
)
switch {
case strings.HasSuffix(d.LocalPath, ".bz2"):
extension = ".bz2"
case strings.HasSuffix(d.LocalPath, ".gz"):
extension = ".gz"
case strings.HasSuffix(d.LocalPath, ".xz"):
extension = ".xz"
}
extension := compressionFromFile(dataDir)
uncompressedFilename := d.VMName + "_" + d.ImageName
return filepath.Join(dataDir, strings.TrimSuffix(uncompressedFilename, extension))
return filepath.Join(dataDir, strings.TrimSuffix(uncompressedFilename, extension.String()))
}
func (g GenericDownload) Get() *Download {
@ -224,11 +214,11 @@ func Decompress(localPath, uncompressedPath string) error {
// Will error out if file without .xz already exists
// Maybe extracting then renaming is a good idea here..
// depends on xz: not pre-installed on mac, so it becomes a brew dependency
// depends on Xz: not pre-installed on mac, so it becomes a brew dependency
func decompressXZ(src string, output io.WriteCloser) error {
var read io.Reader
var cmd *exec.Cmd
// Prefer xz utils for fastest performance, fallback to go xi2 impl
// Prefer Xz utils for fastest performance, fallback to go xi2 impl
if _, err := exec.LookPath("xz"); err == nil {
cmd = exec.Command("xz", "-d", "-c", "-k", src)
read, err = cmd.StdoutPipe()

View file

@ -19,7 +19,7 @@ const (
)
type Provider struct{}
type Virtualization struct{}
// Deprecated: MachineVMV1 is being deprecated in favor a more flexible and informative
// structure

View file

@ -35,12 +35,12 @@ import (
)
var (
qemuProvider = &Provider{}
qemuProvider = &Virtualization{}
// vmtype refers to qemu (vs libvirt, krun, etc).
vmtype = "qemu"
)
func GetVirtualizationProvider() machine.Provider {
func GetVirtualizationProvider() machine.VirtProvider {
return qemuProvider
}
@ -64,7 +64,7 @@ const (
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
@ -231,7 +231,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error {
// LoadVMByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
func (p *Virtualization) LoadVMByName(name string) (machine.VM, error) {
vm := &MachineVM{Name: name}
vm.HostUser = machine.HostUser{UID: -1} // posix reserves -1, so use it to signify undefined
if err := vm.update(); err != nil {
@ -1117,7 +1117,7 @@ func getDiskSize(path string) (uint64, error) {
}
// List lists all vm's that use qemu virtualization
func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return getVMInfos()
}
@ -1193,7 +1193,7 @@ func getVMInfos() ([]*machine.ListResponse, error) {
return listed, err
}
func (p *Provider) IsValidVMName(name string) (bool, error) {
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
infos, err := getVMInfos()
if err != nil {
return false, err
@ -1208,7 +1208,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
// CheckExclusiveActiveVM checks if there is a VM already running
// that does not allow other VMs to be running
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := getVMInfos()
if err != nil {
return false, "", fmt.Errorf("checking VM active: %w", err)
@ -1670,7 +1670,7 @@ func (v *MachineVM) editCmdLine(flag string, value string) {
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *Provider) RemoveAndCleanMachines() error {
func (p *Virtualization) RemoveAndCleanMachines() error {
var (
vm machine.VM
listResponse []*machine.ListResponse
@ -1745,7 +1745,7 @@ func (p *Provider) RemoveAndCleanMachines() error {
return prevErr
}
func (p *Provider) VMType() string {
func (p *Virtualization) VMType() string {
return vmtype
}

View file

@ -43,9 +43,9 @@ func NewFedoraDownloader(vmType, vmName, releaseStream string) (machine.Distribu
f := FedoraDownload{
Download: machine.Download{
Arch: machine.GetFcosArch(),
Artifact: "",
Artifact: machine.None,
CacheDir: cacheDir,
Format: machine.Format,
Format: machine.Tar,
ImageName: imageName,
LocalPath: filepath.Join(cacheDir, imageName),
URL: downloadURL,

View file

@ -28,7 +28,7 @@ import (
)
var (
wslProvider = &Provider{}
wslProvider = &Virtualization{}
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = "wsl"
)
@ -204,7 +204,7 @@ const (
globalPipe = "docker_engine"
)
type Provider struct{}
type Virtualization struct{}
type MachineVM struct {
// ConfigPath is the path to the configuration file
@ -235,12 +235,12 @@ func (e *ExitCodeError) Error() string {
return fmt.Sprintf("Process failed with exit code: %d", e.code)
}
func GetWSLProvider() machine.Provider {
func GetWSLProvider() machine.VirtProvider {
return wslProvider
}
// NewMachine initializes an instance of a wsl machine
func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
func (p *Virtualization) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vm := new(MachineVM)
if len(opts.Name) > 0 {
vm.Name = opts.Name
@ -281,7 +281,7 @@ func getConfigPathExt(name string, extension string) (string, error) {
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
func (p *Virtualization) LoadVMByName(name string) (machine.VM, error) {
configPath, err := getConfigPath(name)
if err != nil {
return nil, err
@ -1418,7 +1418,7 @@ func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
}
// List lists all vm's that use qemu virtualization
func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
func (p *Virtualization) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return GetVMInfos()
}
@ -1547,7 +1547,7 @@ func getMem(vm *MachineVM) (uint64, error) {
return total - available, err
}
func (p *Provider) IsValidVMName(name string) (bool, error) {
func (p *Virtualization) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos()
if err != nil {
return false, err
@ -1560,7 +1560,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) {
return false, nil
}
func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
func (p *Virtualization) CheckExclusiveActiveVM() (bool, string, error) {
return false, "", nil
}
@ -1619,7 +1619,7 @@ func (v *MachineVM) getResources() (resources machine.ResourceConfig) {
}
// RemoveAndCleanMachines removes all machine and cleans up any other files associated with podman machine
func (p *Provider) RemoveAndCleanMachines() error {
func (p *Virtualization) RemoveAndCleanMachines() error {
var (
vm machine.VM
listResponse []*machine.ListResponse
@ -1694,6 +1694,6 @@ func (p *Provider) RemoveAndCleanMachines() error {
return prevErr
}
func (p *Provider) VMType() string {
func (p *Virtualization) VMType() string {
return vmtype
}