Ensure HyperV 9p mounts work when a dir doesn't exist

Before, we required that the mount target exist and be a
directory for the 9p mount to successfully complete, which is not
how things are supposed to work - the user should be able to
mount anywhere. This should just be a simple mkdir, but with FCOS
the root directory is immutable so we need to undo that before we
can mkdir, and unfortunately we don't have a library that can do
chattr (and I didn't want to drag in a new dependency just for
that), so let's be gross and add it to the SSH command. I
aggressively dislike this but it does work.

[NO NEW TESTS NEEDED] Can worry about getting a more generic
mount test together for Machine later.

Signed-off-by: Matt Heon <mheon@redhat.com>
This commit is contained in:
Matt Heon
2024-02-08 14:37:14 -05:00
parent e99ececc2f
commit 26ec570c65
4 changed files with 63 additions and 5 deletions

View File

@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"strconv"
"time"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v5/cmd/podman/registry"
@ -69,8 +70,22 @@ func client9p(portNum uint32, mountPath string) error {
logrus.Infof("Going to mount 9p on vsock port %d to directory %s", portNum, mountPath)
// Host connects to non-hypervisor processes on the host running the VM.
conn, err := vsock.Dial(vsock.Host, portNum, nil)
// The server is starting at the same time.
// Perform up to 5 retries with a backoff.
var (
conn *vsock.Conn
retries = 20
)
for i := 0; i < retries; i++ {
// Host connects to non-hypervisor processes on the host running the VM.
conn, err = vsock.Dial(vsock.Host, portNum, nil)
// If errors.Is worked on this error, we could detect non-timeout errors.
// But it doesn't. So retry 5 times regardless.
if err == nil {
break
}
time.Sleep(250 * time.Millisecond)
}
if err != nil {
return fmt.Errorf("dialing vsock port %d: %w", portNum, err)
}

View File

@ -1,10 +1,14 @@
package e2e_test
import (
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"time"
. "github.com/onsi/ginkgo/v2"
@ -93,6 +97,31 @@ var _ = Describe("run basic podman commands", func() {
Expect(out).ToNot(ContainSubstring("gvproxy"))
})
It("podman volume on non-standard path", func() {
skipIfWSL("Requires standard volume handling")
dir, err := os.MkdirTemp("", "machine-volume")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(dir)
testString := "abcdefg1234567"
testFile := "testfile"
err = os.WriteFile(filepath.Join(dir, testFile), []byte(testString), 0644)
Expect(err).ToNot(HaveOccurred())
name := randomString()
machinePath := "/does/not/exist"
init := new(initMachine).withVolume(fmt.Sprintf("%s:%s", dir, machinePath)).withImagePath(mb.imagePath).withNow()
session, err := mb.setName(name).setCmd(init).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(0))
// Must use path.Join to ensure forward slashes are used, even on Windows.
ssh := new(sshMachine).withSSHCommand([]string{"cat", path.Join(machinePath, testFile)})
ls, err := mb.setName(name).setCmd(ssh).run()
Expect(err).ToNot(HaveOccurred())
Expect(ls).To(Exit(0))
Expect(ls.outputToString()).To(ContainSubstring(testString))
})
})
func testHTTPServer(port string, shouldErr bool, expectedResponse string) {

View File

@ -423,7 +423,7 @@ func (h HyperVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo b
for _, mount := range mc.Mounts {
if mount.VSockNumber == nil {
return fmt.Errorf("mount %s has not vsock port defined", mount.Source)
return fmt.Errorf("mount %s has no vsock port defined", mount.Source)
}
p9ServerArgs = append(p9ServerArgs, "--serve", fmt.Sprintf("%s:%s", mount.Source, winio.VsockServiceID(uint32(*mount.VSockNumber)).String()))
}

View File

@ -5,6 +5,8 @@ package hyperv
import (
"errors"
"fmt"
"path"
"strings"
"github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/hyperv/vsock"
@ -40,7 +42,19 @@ func removeShares(mc *vmconfigs.MachineConfig) error {
func startShares(mc *vmconfigs.MachineConfig) error {
for _, mount := range mc.Mounts {
args := []string{"-q", "--", "sudo", "podman"}
args := []string{"-q", "--"}
cleanTarget := path.Clean(mount.Target)
requiresChattr := !strings.HasPrefix(cleanTarget, "/home") && !strings.HasPrefix(cleanTarget, "/mnt")
if requiresChattr {
args = append(args, "sudo", "chattr", "-i", "/", "; ")
}
args = append(args, "sudo", "mkdir", "-p", cleanTarget, "; ")
if requiresChattr {
args = append(args, "sudo", "chattr", "+i", "/", "; ")
}
args = append(args, "sudo", "podman")
if logrus.IsLevelEnabled(logrus.DebugLevel) {
args = append(args, "--log-level=debug")
}
@ -48,7 +62,7 @@ func startShares(mc *vmconfigs.MachineConfig) error {
if mount.VSockNumber == nil {
return errors.New("cannot start 9p shares with undefined vsock number")
}
args = append(args, "machine", "client9p", fmt.Sprintf("%d", mount.VSockNumber), mount.Target)
args = append(args, "machine", "client9p", fmt.Sprintf("%d", *mount.VSockNumber), mount.Target)
if err := machine.CommonSSH(mc.SSH.RemoteUsername, mc.SSH.IdentityPath, mc.Name, mc.SSH.Port, args); err != nil {
return err