Add podman machine test suite

This PR introduces a test suite for podman machine.  It can currently be
run on developers' local machines and is not part of the official CI
testing; however, the expectation is that any work on machine should
come with an accompanying test.

At present, the test must be run on Linux.  It is untested on Darwin.
There is no Makefile target for the test.  It can be run like `ginkgo -v
pkg/machine/test/.`.  It should be run as a unprivileged user.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2022-04-02 15:25:19 -05:00
parent 6984a0f357
commit 833456e079
23 changed files with 1134 additions and 24 deletions

View File

@ -537,7 +537,7 @@ localunit: test/goecho/goecho test/version/version
UNIT=1 $(GOBIN)/ginkgo \
-r \
$(TESTFLAGS) \
--skipPackage test/e2e,pkg/apparmor,pkg/bindings,hack \
--skipPackage test/e2e,pkg/apparmor,pkg/bindings,hack,pkg/machine/e2e \
--cover \
--covermode atomic \
--coverprofile coverprofile \

View File

@ -135,7 +135,6 @@ func initMachine(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if finished, err := vm.Init(initOpts); err != nil || !finished {
// Finished = true, err = nil - Success! Log a message with further instructions
// Finished = false, err = nil - The installation is partially complete and podman should
@ -144,7 +143,6 @@ func initMachine(cmd *cobra.Command, args []string) error {
// - a user has chosen to perform their own reboot
// - reexec for limited admin operations, returning to parent
// Finished = *, err != nil - Exit with an error message
return err
}
fmt.Println("Machine init complete")

View File

@ -10,9 +10,9 @@ BUILD_TAGS[abi]="${BUILD_TAGS[default]},!remoteclient"
BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},remote,remoteclient"
declare -A SKIP_DIRS
SKIP_DIRS[abi]=""
SKIP_DIRS[abi]="pkg/machine/e2e"
# TODO: add "remote" build tag to pkg/api
SKIP_DIRS[tunnel]="pkg/api"
SKIP_DIRS[tunnel]="pkg/api,pkg/machine/e2e"
[[ $1 == run ]] && shift

View File

@ -0,0 +1,50 @@
package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)
var _ = Describe("run basic podman commands", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("Basic ops", func() {
name := randomString(12)
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
Expect(session).To(Exit(0))
bm := basicMachine{}
imgs, err := mb.setCmd(bm.withPodmanCommand([]string{"images", "-q"})).run()
Expect(err).To(BeNil())
Expect(imgs).To(Exit(0))
Expect(len(imgs.outputToStringSlice())).To(Equal(0))
newImgs, err := mb.setCmd(bm.withPodmanCommand([]string{"pull", "quay.io/libpod/alpine_nginx"})).run()
Expect(err).To(BeNil())
Expect(newImgs).To(Exit(0))
Expect(len(newImgs.outputToStringSlice())).To(Equal(1))
runAlp, err := mb.setCmd(bm.withPodmanCommand([]string{"run", "quay.io/libpod/alpine_nginx", "cat", "/etc/os-release"})).run()
Expect(err).To(BeNil())
Expect(runAlp).To(Exit(0))
Expect(runAlp.outputToString()).To(ContainSubstring("Alpine Linux"))
rmCon, err := mb.setCmd(bm.withPodmanCommand([]string{"rm", "-a"})).run()
Expect(err).To(BeNil())
Expect(rmCon).To(Exit(0))
})
})

174
pkg/machine/e2e/config.go Normal file
View File

@ -0,0 +1,174 @@
package e2e
import (
"encoding/json"
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/containers/podman/v4/pkg/machine"
"github.com/containers/podman/v4/pkg/machine/qemu"
"github.com/containers/podman/v4/pkg/util"
. "github.com/onsi/ginkgo" //nolint:golint,stylecheck
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
. "github.com/onsi/gomega/gexec" //nolint:golint,stylecheck
)
var originalHomeDir = os.Getenv("HOME")
const (
defaultTimeout time.Duration = 90 * time.Second
)
type machineCommand interface {
buildCmd(m *machineTestBuilder) []string
}
type MachineTestBuilder interface {
setName(string) *MachineTestBuilder
setCmd(mc machineCommand) *MachineTestBuilder
setTimeout(duration time.Duration) *MachineTestBuilder
run() (*machineSession, error)
}
type machineSession struct {
*gexec.Session
}
type machineTestBuilder struct {
cmd []string
imagePath string
name string
names []string
podmanBinary string
timeout time.Duration
}
type qemuMachineInspectInfo struct {
State machine.Status
VM qemu.MachineVM
}
// waitWithTimeout waits for a command to complete for a given
// number of seconds
func (ms *machineSession) waitWithTimeout(timeout time.Duration) {
Eventually(ms, timeout).Should(Exit())
os.Stdout.Sync()
os.Stderr.Sync()
}
func (ms *machineSession) Bytes() []byte {
return []byte(ms.outputToString())
}
func (ms *machineSession) outputToStringSlice() []string {
var results []string
output := string(ms.Out.Contents())
for _, line := range strings.Split(output, "\n") {
if line != "" {
results = append(results, line)
}
}
return results
}
// outputToString returns the output from a session in string form
func (ms *machineSession) outputToString() string {
if ms == nil || ms.Out == nil || ms.Out.Contents() == nil {
return ""
}
fields := strings.Fields(string(ms.Out.Contents()))
return strings.Join(fields, " ")
}
// newMB constructor for machine test builders
func newMB() (*machineTestBuilder, error) {
mb := machineTestBuilder{
timeout: defaultTimeout,
}
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
mb.podmanBinary = filepath.Join(cwd, "../../../bin/podman-remote")
if os.Getenv("PODMAN_BINARY") != "" {
mb.podmanBinary = os.Getenv("PODMAN_BINARY")
}
return &mb, nil
}
// setName sets the name of the virtuaql machine for the command
func (m *machineTestBuilder) setName(name string) *machineTestBuilder {
m.name = name
return m
}
// setCmd takes a machineCommand struct and assembles a cmd line
// representation of the podman machine command
func (m *machineTestBuilder) setCmd(mc machineCommand) *machineTestBuilder {
// If no name for the machine exists, we set a random name.
if !util.StringInSlice(m.name, m.names) {
if len(m.name) < 1 {
m.name = randomString(12)
}
m.names = append(m.names, m.name)
}
m.cmd = mc.buildCmd(m)
return m
}
func (m *machineTestBuilder) setTimeout(timeout time.Duration) *machineTestBuilder {
m.timeout = timeout
return m
}
// toQemuInspectInfo is only for inspecting qemu machines. Other providers will need
// to make their own.
func (mb *machineTestBuilder) toQemuInspectInfo() ([]qemuMachineInspectInfo, int, error) {
args := []string{"machine", "inspect"}
args = append(args, mb.names...)
session, err := runWrapper(mb.podmanBinary, args, defaultTimeout)
if err != nil {
return nil, -1, err
}
mii := []qemuMachineInspectInfo{}
err = json.Unmarshal(session.Bytes(), &mii)
return mii, session.ExitCode(), err
}
func (m *machineTestBuilder) run() (*machineSession, error) {
return runWrapper(m.podmanBinary, m.cmd, m.timeout)
}
func runWrapper(podmanBinary string, cmdArgs []string, timeout time.Duration) (*machineSession, error) {
if len(os.Getenv("DEBUG")) > 0 {
cmdArgs = append([]string{"--log-level=debug"}, cmdArgs...)
}
fmt.Println(podmanBinary + " " + strings.Join(cmdArgs, " "))
c := exec.Command(podmanBinary, cmdArgs...)
session, err := Start(c, GinkgoWriter, GinkgoWriter)
if err != nil {
Fail(fmt.Sprintf("Unable to start session: %q", err))
return nil, err
}
ms := machineSession{session}
ms.waitWithTimeout(timeout)
fmt.Println("output:", ms.outputToString())
return &ms, nil
}
func (m *machineTestBuilder) init() {}
// randomString returns a string of given length composed of random characters
func randomString(n int) string {
var randomLetters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = randomLetters[rand.Intn(len(randomLetters))]
}
return string(b)
}

View File

@ -0,0 +1,19 @@
package e2e
type basicMachine struct {
args []string
cmd []string
}
func (s basicMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"-r"}
if len(s.args) > 0 {
cmd = append(cmd, s.args...)
}
return cmd
}
func (s *basicMachine) withPodmanCommand(args []string) *basicMachine {
s.args = args
return s
}

View File

@ -0,0 +1,101 @@
package e2e
import (
"strconv"
)
type initMachine struct {
/*
--cpus uint Number of CPUs (default 1)
--disk-size uint Disk size in GB (default 100)
--ignition-path string Path to ignition file
--image-path string Path to qcow image (default "testing")
-m, --memory uint Memory in MB (default 2048)
--now Start machine now
--rootful Whether this machine should prefer rootful container exectution
--timezone string Set timezone (default "local")
-v, --volume stringArray Volumes to mount, source:target
--volume-driver string Optional volume driver
*/
cpus *uint
diskSize *uint
ignitionPath string
imagePath string
memory *uint
now bool
timezone string
volumes []string
cmd []string
}
func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "init"}
if i.cpus != nil {
cmd = append(cmd, "--cpus", strconv.Itoa(int(*i.cpus)))
}
if i.diskSize != nil {
cmd = append(cmd, "--disk-size", strconv.Itoa(int(*i.diskSize)))
}
if l := len(i.ignitionPath); l > 0 {
cmd = append(cmd, "--ignition-path", i.ignitionPath)
}
if l := len(i.imagePath); l > 0 {
cmd = append(cmd, "--image-path", i.imagePath)
}
if i.memory != nil {
cmd = append(cmd, "--memory", strconv.Itoa(int(*i.memory)))
}
if l := len(i.timezone); l > 0 {
cmd = append(cmd, "--timezone", i.timezone)
}
for _, v := range i.volumes {
cmd = append(cmd, "--volume", v)
}
if i.now {
cmd = append(cmd, "--now")
}
cmd = append(cmd, m.name)
i.cmd = cmd
return cmd
}
func (i *initMachine) withCPUs(num uint) *initMachine {
i.cpus = &num
return i
}
func (i *initMachine) withDiskSize(size uint) *initMachine {
i.diskSize = &size
return i
}
func (i *initMachine) withIgnitionPath(path string) *initMachine {
i.ignitionPath = path
return i
}
func (i *initMachine) withImagePath(path string) *initMachine {
i.imagePath = path
return i
}
func (i *initMachine) withMemory(num uint) *initMachine {
i.memory = &num
return i
}
func (i *initMachine) withNow() *initMachine {
i.now = true
return i
}
func (i *initMachine) withTimezone(tz string) *initMachine {
i.timezone = tz
return i
}
func (i *initMachine) withVolume(v string) *initMachine {
i.volumes = append(i.volumes, v)
return i
}

View File

@ -0,0 +1,24 @@
package e2e
type inspectMachine struct {
/*
--format string Format volume output using JSON or a Go template
*/
cmd []string
format string
}
func (i *inspectMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "inspect"}
if len(i.format) > 0 {
cmd = append(cmd, "--format", i.format)
}
cmd = append(cmd, m.names...)
i.cmd = cmd
return cmd
}
func (i *inspectMachine) withFormat(format string) *inspectMachine {
i.format = format
return i
}

View File

@ -0,0 +1,45 @@
package e2e
type listMachine struct {
/*
--format string Format volume output using JSON or a Go template (default "{{.Name}}\t{{.VMType}}\t{{.Created}}\t{{.LastUp}}\t{{.CPUs}}\t{{.Memory}}\t{{.DiskSize}}\n")
--noheading Do not print headers
-q, --quiet Show only machine names
*/
format string
noHeading bool
quiet bool
cmd []string
}
func (i *listMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "list"}
if len(i.format) > 0 {
cmd = append(cmd, "--format", i.format)
}
if i.noHeading {
cmd = append(cmd, "--noheading")
}
if i.quiet {
cmd = append(cmd, "--quiet")
}
i.cmd = cmd
return cmd
}
func (i *listMachine) withNoHeading() *listMachine {
i.noHeading = true
return i
}
func (i *listMachine) withQuiet() *listMachine {
i.quiet = true
return i
}
func (i *listMachine) withFormat(format string) *listMachine {
i.format = format
return i
}

View File

@ -0,0 +1,56 @@
package e2e
type rmMachine struct {
/*
-f, --force Stop and do not prompt before rming
--save-ignition Do not delete ignition file
--save-image Do not delete the image file
--save-keys Do not delete SSH keys
*/
force bool
saveIgnition bool
saveImage bool
saveKeys bool
cmd []string
}
func (i *rmMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "rm"}
if i.force {
cmd = append(cmd, "--force")
}
if i.saveIgnition {
cmd = append(cmd, "--save-ignition")
}
if i.saveImage {
cmd = append(cmd, "--save-image")
}
if i.saveKeys {
cmd = append(cmd, "--save-keys")
}
cmd = append(cmd, m.name)
i.cmd = cmd
return cmd
}
func (i *rmMachine) withForce() *rmMachine {
i.force = true
return i
}
func (i *rmMachine) withSaveIgnition() *rmMachine {
i.saveIgnition = true
return i
}
func (i *rmMachine) withSaveImage() *rmMachine {
i.saveImage = true
return i
}
func (i *rmMachine) withSaveKeys() *rmMachine {
i.saveKeys = true
return i
}

View File

@ -0,0 +1,33 @@
package e2e
type sshMachine struct {
/*
--username string Username to use when ssh-ing into the VM.
*/
username string
sshCommand []string
cmd []string
}
func (s sshMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "ssh"}
if len(m.name) > 0 {
cmd = append(cmd, m.name)
}
if len(s.sshCommand) > 0 {
cmd = append(cmd, s.sshCommand...)
}
return cmd
}
func (s *sshMachine) withUsername(name string) *sshMachine {
s.username = name
return s
}
func (s *sshMachine) withSSHComand(sshCommand []string) *sshMachine {
s.sshCommand = sshCommand
return s
}

View File

@ -0,0 +1,16 @@
package e2e
type startMachine struct {
/*
No command line args other than a machine vm name (also not required)
*/
cmd []string
}
func (s startMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "start"}
if len(m.name) > 0 {
cmd = append(cmd, m.name)
}
return cmd
}

View File

@ -0,0 +1,16 @@
package e2e
type stopMachine struct {
/*
No command line args other than a machine vm name (also not required)
*/
cmd []string
}
func (s stopMachine) buildCmd(m *machineTestBuilder) []string {
cmd := []string{"machine", "stop"}
if len(m.name) > 0 {
cmd = append(cmd, m.name)
}
return cmd
}

View File

@ -0,0 +1,77 @@
package e2e
import (
"time"
"github.com/containers/podman/v4/pkg/machine"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine init", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("bad init name", func() {
i := initMachine{}
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(125))
})
It("simple init", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
inspectBefore, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
Expect(ec).To(BeZero())
Expect(len(inspectBefore)).To(BeNumerically(">", 0))
testMachine := inspectBefore[0]
Expect(testMachine.VM.Name).To(Equal(mb.names[0]))
Expect(testMachine.VM.CPUs).To(Equal(uint64(1)))
Expect(testMachine.VM.Memory).To(Equal(uint64(2048)))
})
It("simple init with start", func() {
i := initMachine{}
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
inspectBefore, ec, err := mb.toQemuInspectInfo()
Expect(ec).To(BeZero())
Expect(len(inspectBefore)).To(BeNumerically(">", 0))
Expect(err).To(BeNil())
Expect(len(inspectBefore)).To(BeNumerically(">", 0))
Expect(inspectBefore[0].VM.Name).To(Equal(mb.names[0]))
s := startMachine{}
ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run()
Expect(err).To(BeNil())
Expect(ssession).Should(Exit(0))
inspectAfter, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
Expect(ec).To(BeZero())
Expect(len(inspectBefore)).To(BeNumerically(">", 0))
Expect(len(inspectAfter)).To(BeNumerically(">", 0))
Expect(inspectAfter[0].State).To(Equal(machine.Running))
})
})

View File

@ -0,0 +1,67 @@
package e2e
import (
"encoding/json"
"github.com/containers/podman/v4/pkg/machine/qemu"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("podman machine stop", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("inspect bad name", func() {
i := inspectMachine{}
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(125))
})
It("inspect two machines", func() {
i := new(initMachine)
foo1, err := mb.setName("foo1").setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(foo1.ExitCode()).To(Equal(0))
ii := new(initMachine)
foo2, err := mb.setName("foo2").setCmd(ii.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(foo2.ExitCode()).To(Equal(0))
inspect := new(inspectMachine)
inspectSession, err := mb.setName("foo1").setCmd(inspect).run()
Expect(err).To(BeNil())
Expect(inspectSession.ExitCode()).To(Equal(0))
type fakeInfos struct {
Status string
VM qemu.MachineVM
}
infos := make([]fakeInfos, 0, 2)
err = json.Unmarshal(inspectSession.Bytes(), &infos)
Expect(err).ToNot(HaveOccurred())
Expect(len(infos)).To(Equal(2))
//rm := new(rmMachine)
//// Must manually clean up due to multiple names
//for _, name := range []string{"foo1", "foo2"} {
// mb.setName(name).setCmd(rm.withForce()).run()
// mb.names = []string{}
//}
//mb.names = []string{}
})
})

View File

@ -0,0 +1,79 @@
package e2e
import (
"strings"
"github.com/containers/buildah/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gexec"
)
var _ = Describe("podman machine list", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("list machine", func() {
list := new(listMachine)
firstList, err := mb.setCmd(list).run()
Expect(err).NotTo(HaveOccurred())
Expect(firstList).Should(Exit(0))
Expect(len(firstList.outputToStringSlice())).To(Equal(1)) // just the header
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session).To(Exit(0))
secondList, err := mb.setCmd(list).run()
Expect(err).NotTo(HaveOccurred())
Expect(secondList).To(Exit(0))
Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // one machine and the header
})
It("list machines with quiet", func() {
// Random names for machines to test list
name1 := randomString(12)
name2 := randomString(12)
list := new(listMachine)
firstList, err := mb.setCmd(list.withQuiet()).run()
Expect(err).NotTo(HaveOccurred())
Expect(firstList).Should(Exit(0))
Expect(len(firstList.outputToStringSlice())).To(Equal(0)) // No header with quiet
i := new(initMachine)
session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session).To(Exit(0))
session2, err := mb.setName(name2).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session2).To(Exit(0))
secondList, err := mb.setCmd(list.withQuiet()).run()
Expect(err).NotTo(HaveOccurred())
Expect(secondList).To(Exit(0))
Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // two machines, no header
listNames := secondList.outputToStringSlice()
stripAsterisk(listNames)
Expect(util.StringInSlice(name1, listNames)).To(BeTrue())
Expect(util.StringInSlice(name2, listNames)).To(BeTrue())
})
})
func stripAsterisk(sl []string) {
for idx, val := range sl {
sl[idx] = strings.TrimRight(val, "*")
}
}

View File

@ -0,0 +1,129 @@
package e2e
import (
"fmt"
"io"
"io/ioutil"
url2 "net/url"
"os"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/containers/podman/v4/pkg/machine"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
const (
defaultStream string = "podman-testing"
tmpDir string = "/var/tmp"
)
var (
fqImageName string
suiteImageName string
)
// TestLibpod ginkgo master function
func TestMachine(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Podman Machine tests")
}
var _ = BeforeSuite(func() {
fcd, err := machine.GetFCOSDownload(defaultStream)
if err != nil {
Fail("unable to get virtual machine image")
}
suiteImageName = strings.TrimSuffix(path.Base(fcd.Location), ".xz")
fqImageName = filepath.Join(tmpDir, suiteImageName)
if _, err := os.Stat(fqImageName); err != nil {
if os.IsNotExist(err) {
getMe, err := url2.Parse(fcd.Location)
if err != nil {
Fail(fmt.Sprintf("unable to create url for download: %q", err))
}
now := time.Now()
if err := machine.DownloadVMImage(getMe, fqImageName+".xz"); err != nil {
Fail(fmt.Sprintf("unable to download machine image: %q", err))
}
fmt.Println("Download took: ", time.Since(now).String())
if err := machine.Decompress(fqImageName+".xz", fqImageName); err != nil {
Fail(fmt.Sprintf("unable to decompress image file: %q", err))
}
} else {
Fail(fmt.Sprintf("unable to check for cache image: %q", err))
}
}
})
var _ = SynchronizedAfterSuite(func() {},
func() {
fmt.Println("After")
})
func setup() (string, *machineTestBuilder) {
homeDir, err := ioutil.TempDir("/var/tmp", "podman_test")
if err != nil {
Fail(fmt.Sprintf("failed to create home directory: %q", err))
}
if err := os.MkdirAll(filepath.Join(homeDir, ".ssh"), 0700); err != nil {
Fail(fmt.Sprintf("failed to create ssh dir: %q", err))
}
sshConfig, err := os.Create(filepath.Join(homeDir, ".ssh", "config"))
if err != nil {
Fail(fmt.Sprintf("failed to create ssh config: %q", err))
}
if _, err := sshConfig.WriteString("IdentitiesOnly=yes"); err != nil {
Fail(fmt.Sprintf("failed to write ssh config: %q", err))
}
if err := sshConfig.Close(); err != nil {
Fail(fmt.Sprintf("unable to close ssh config file descriptor: %q", err))
}
if err := os.Setenv("HOME", homeDir); err != nil {
Fail("failed to set home dir")
}
if err := os.Unsetenv("SSH_AUTH_SOCK"); err != nil {
Fail("unable to unset SSH_AUTH_SOCK")
}
mb, err := newMB()
if err != nil {
Fail(fmt.Sprintf("failed to create machine test: %q", err))
}
f, err := os.Open(fqImageName)
if err != nil {
Fail(fmt.Sprintf("failed to open file %s: %q", fqImageName, err))
}
mb.imagePath = filepath.Join(homeDir, suiteImageName)
n, err := os.Create(mb.imagePath)
if err != nil {
Fail(fmt.Sprintf("failed to create file %s: %q", mb.imagePath, err))
}
if _, err := io.Copy(n, f); err != nil {
Fail(fmt.Sprintf("failed to copy %ss to %s: %q", fqImageName, mb.imagePath, err))
}
return homeDir, mb
}
func teardown(origHomeDir string, testDir string, mb *machineTestBuilder) {
s := new(stopMachine)
for _, name := range mb.names {
if _, err := mb.setName(name).setCmd(s).run(); err != nil {
fmt.Printf("error occured rm'ing machine: %q\n", err)
}
}
if err := os.RemoveAll(testDir); err != nil {
Fail(fmt.Sprintf("failed to remove test dir: %q", err))
}
// this needs to be last in teardown
if err := os.Setenv("HOME", origHomeDir); err != nil {
Fail("failed to set home dir")
}
}

View File

@ -0,0 +1,67 @@
package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("podman machine rm", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("bad init name", func() {
i := rmMachine{}
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(125))
})
It("Remove machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
rm := rmMachine{}
_, err = mb.setCmd(rm.withForce()).run()
Expect(err).To(BeNil())
// Inspecting a non-existent machine should fail
// which means it is gone
_, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
Expect(ec).To(Equal(125))
})
It("Remove running machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
rm := new(rmMachine)
// Removing a running machine should fail
stop, err := mb.setCmd(rm).run()
Expect(err).To(BeNil())
Expect(stop.ExitCode()).To(Equal(125))
// Removing again with force
stopAgain, err := mb.setCmd(rm.withForce()).run()
Expect(err).To(BeNil())
Expect(stopAgain.ExitCode()).To(BeZero())
// Inspect to be dead sure
_, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
Expect(ec).To(Equal(125))
})
})

View File

@ -0,0 +1,59 @@
package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("podman machine ssh", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("bad machine name", func() {
name := randomString(12)
ssh := sshMachine{}
session, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(125))
// TODO seems like stderr is not being returned; re-enabled when fixed
//Expect(session.outputToString()).To(ContainSubstring("not exist"))
})
It("ssh to non-running machine", func() {
name := randomString(12)
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
ssh := sshMachine{}
sshSession, err := mb.setName(name).setCmd(ssh).run()
Expect(err).To(BeNil())
// TODO seems like stderr is not being returned; re-enabled when fixed
//Expect(sshSession.outputToString()).To(ContainSubstring("is not running"))
Expect(sshSession.ExitCode()).To(Equal(125))
})
It("ssh to running machine and check os-type", func() {
name := randomString(12)
i := new(initMachine)
session, err := mb.setName(name).setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
ssh := sshMachine{}
sshSession, err := mb.setName(name).setCmd(ssh.withSSHComand([]string{"cat", "/etc/os-release"})).run()
Expect(err).To(BeNil())
Expect(sshSession.ExitCode()).To(Equal(0))
Expect(sshSession.outputToString()).To(ContainSubstring("Fedora CoreOS"))
})
})

View File

@ -0,0 +1,36 @@
package e2e
import (
"github.com/containers/podman/v4/pkg/machine"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("podman machine start", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("start simple machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
s := new(startMachine)
startSession, err := mb.setCmd(s).run()
Expect(err).To(BeNil())
Expect(startSession.ExitCode()).To(Equal(0))
info, ec, err := mb.toQemuInspectInfo()
Expect(err).To(BeNil())
Expect(ec).To(BeZero())
Expect(info[0].State).To(Equal(machine.Running))
})
})

View File

@ -0,0 +1,46 @@
package e2e
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("podman machine stop", func() {
var (
mb *machineTestBuilder
testDir string
)
BeforeEach(func() {
testDir, mb = setup()
})
AfterEach(func() {
teardown(originalHomeDir, testDir, mb)
})
It("stop bad name", func() {
i := stopMachine{}
reallyLongName := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
session, err := mb.setName(reallyLongName).setCmd(&i).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(125))
})
It("Stop running machine", func() {
i := new(initMachine)
session, err := mb.setCmd(i.withImagePath(mb.imagePath).withNow()).run()
Expect(err).To(BeNil())
Expect(session.ExitCode()).To(Equal(0))
stop := new(stopMachine)
// Removing a running machine should fail
stopSession, err := mb.setCmd(stop).run()
Expect(err).To(BeNil())
Expect(stopSession.ExitCode()).To(Equal(0))
// Stopping it again should not result in an error
stopAgain, err := mb.setCmd(stop).run()
Expect(err).To(BeNil())
Expect(stopAgain.ExitCode()).To(BeZero())
})
})

View File

@ -43,7 +43,7 @@ type FcosDownload struct {
}
func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload, error) {
info, err := getFCOSDownload(imageStream)
info, err := GetFCOSDownload(imageStream)
if err != nil {
return nil, err
}
@ -79,7 +79,7 @@ func (f FcosDownload) Get() *Download {
return &f.Download
}
type fcosDownloadInfo struct {
type FcosDownloadInfo struct {
CompressionType string
Location string
Release string
@ -139,7 +139,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) { // nolint:staticcheck,unparam
func GetFCOSDownload(imageStream string) (*FcosDownloadInfo, error) { //nolint:staticcheck
var (
fcosstable stream.Stream
altMeta release.Release
@ -150,8 +150,8 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
// fcos trees, we should remove it and re-release at least on
// macs.
// TODO: remove when podman4.0 is in coreos
// nolint:staticcheck
imageStream = "podman-testing"
imageStream = "podman-testing" //nolint:staticcheck
switch imageStream {
case "podman-testing":
@ -194,7 +194,7 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
}
disk := qcow2.Disk
return &fcosDownloadInfo{
return &FcosDownloadInfo{
Location: disk.Location,
Sha256Sum: disk.Sha256,
CompressionType: "xz",
@ -228,7 +228,7 @@ func getFCOSDownload(imageStream string) (*fcosDownloadInfo, error) { // nolint:
if disk == nil {
return nil, fmt.Errorf("unable to pull VM image: no disk in stream")
}
return &fcosDownloadInfo{
return &FcosDownloadInfo{
Location: disk.Location,
Release: qemu.Release,
Sha256Sum: disk.Sha256,

View File

@ -76,7 +76,6 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
return nil, err
}
vm.IgnitionFilePath = *ignitionFile
imagePath, err := NewMachineFile(opts.ImagePath, nil)
if err != nil {
return nil, err
@ -373,7 +372,6 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err := v.writeConfig(); err != nil {
return false, fmt.Errorf("writing JSON file: %w", err)
}
// User has provided ignition file so keygen
// will be skipped.
if len(opts.IgnitionPath) < 1 {
@ -387,7 +385,6 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
if err := v.prepare(); err != nil {
return false, err
}
originalDiskSize, err := getDiskSize(v.getImageFile())
if err != nil {
return false, err
@ -514,17 +511,28 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
time.Sleep(wait)
wait++
}
defer qemuSocketConn.Close()
if err != nil {
return err
}
fd, err := qemuSocketConn.(*net.UnixConn).File()
if err != nil {
return err
}
defer fd.Close()
dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
if err != nil {
return err
}
defer dnr.Close()
dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
if err != nil {
return err
}
defer dnw.Close()
attr := new(os.ProcAttr)
files := []*os.File{os.Stdin, os.Stdout, os.Stderr, fd}
files := []*os.File{dnr, dnw, dnw, fd}
attr.Files = files
logrus.Debug(v.CmdLine)
cmd := v.CmdLine
@ -552,7 +560,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
}
_, err = os.StartProcess(cmd[0], cmd, attr)
if err != nil {
return err
return errors.Wrapf(err, "unable to execute %q", cmd)
}
}
fmt.Println("Waiting for VM ...")
@ -575,11 +583,11 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if err != nil {
return err
}
defer conn.Close()
_, err = bufio.NewReader(conn).ReadString('\n')
if err != nil {
return err
}
if len(v.Mounts) > 0 {
state, err := v.State()
if err != nil {
@ -918,7 +926,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error {
sshDestination := username + "@localhost"
port := strconv.Itoa(v.Port)
args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"}
if len(opts.Args) > 0 {
args = append(args, opts.Args...)
} else {
@ -1085,9 +1093,19 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
}
attr := new(os.ProcAttr)
// Pass on stdin, stdout, stderr
files := []*os.File{os.Stdin, os.Stdout, os.Stderr}
attr.Files = files
dnr, err := os.OpenFile("/dev/null", os.O_RDONLY, 0755)
if err != nil {
return "", noForwarding, err
}
dnw, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755)
if err != nil {
return "", noForwarding, err
}
defer dnr.Close()
defer dnw.Close()
attr.Files = []*os.File{dnr, dnw, dnw}
cmd := []string{binary}
cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", v.QMPMonitor.Address.GetPath()), "-pid-file", v.PidFilePath.GetPath()}...)
// Add the ssh port
@ -1104,7 +1122,7 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
fmt.Println(cmd)
}
_, err = os.StartProcess(cmd[0], cmd, attr)
return forwardSock, state, err
return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd)
}
func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {