Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix startup data corruption on persistent rootfs #776

Merged
merged 4 commits into from
Oct 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deploy/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ function demo::fix-mounts {
docker exec "${virtlet_node}" mount --make-shared /boot
fi
docker exec "${virtlet_node}" mount --make-shared /sys/fs/cgroup
demo::step "Bind-mounting /var/lib/virtlet from a docker volume"
docker exec "${virtlet_node}" mkdir -p /dind/virtlet /var/lib/virtlet
docker exec "${virtlet_node}" mount --bind /var/lib/virtlet /dind/virtlet
}

function demo::inject-local-image {
Expand Down
45 changes: 41 additions & 4 deletions pkg/libvirttools/persistentroot_volumesource.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import (
"encoding/hex"
"fmt"

"github.com/golang/glog"
libvirtxml "github.com/libvirt/libvirt-go-xml"
digest "github.com/opencontainers/go-digest"
"golang.org/x/sys/unix"

"github.com/Mirantis/virtlet/pkg/blockdev"
"github.com/Mirantis/virtlet/pkg/metadata/types"
Expand Down Expand Up @@ -54,24 +56,33 @@ func (v *persistentRootVolume) dmPath() string {
}

func (v *persistentRootVolume) copyImageToDev(imagePath string) error {
_, err := v.owner.Commander().Command("qemu-img", "convert", "-O", "raw", imagePath, v.dmPath()).Run(nil)
return err
syncFiles(imagePath, v.dev.HostPath, v.dmPath())
if _, err := v.owner.Commander().Command("qemu-img", "convert", "-O", "raw", imagePath, v.dmPath()).Run(nil); err != nil {
return err
}
syncFiles(v.dmPath())
return nil
}

func (v *persistentRootVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.DomainFilesystem, error) {
glog.V(4).Infof("Persistent rootfs setup on %q", v.dev.HostPath)
imagePath, imageDigest, imageSize, err := v.owner.ImageManager().GetImagePathDigestAndVirtualSize(v.config.Image)
if err != nil {
glog.V(4).Infof("Persistent rootfs setup on %q: image info error: %v", v.dev.HostPath, err)
return nil, nil, err
}

if imageDigest.Algorithm() != digest.SHA256 {
glog.V(4).Infof("Persistent rootfs setup on %q: image info error: %v", v.dev.HostPath, err)
return nil, nil, fmt.Errorf("unsupported digest algorithm %q", imageDigest.Algorithm())
}
imageHash, err := hex.DecodeString(imageDigest.Hex())
if err != nil {
glog.V(4).Infof("Persistent rootfs setup on %q: bad digest hex: %q", v.dev.HostPath, imageDigest.Hex())
return nil, nil, fmt.Errorf("bad digest hex: %q", imageDigest.Hex())
}
if len(imageHash) != sha256.Size {
glog.V(4).Infof("Persistent rootfs setup on %q: bad digest size: %q", v.dev.HostPath, imageDigest.Hex())
return nil, nil, fmt.Errorf("bad digest size: %q", imageDigest.Hex())
}

Expand All @@ -81,14 +92,21 @@ func (v *persistentRootVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.Doma
headerMatches, err := ldh.EnsureDevHeaderMatches(v.dev.HostPath, hash)

if err == nil {
glog.V(4).Infof("Persistent rootfs setup on %q: headerMatches: %v", v.dev.HostPath, headerMatches)
err = ldh.Map(v.dev.HostPath, v.dmName(), imageSize)
}

if err == nil && !headerMatches {
err = v.copyImageToDev(imagePath)
if err == nil {
if headerMatches {
glog.V(4).Infof("Persistent rootfs setup on %q: header matches image %q, not overwriting", v.dev.HostPath, imagePath)
} else {
glog.V(4).Infof("Persistent rootfs setup on %q: writing image from %q", v.dev.HostPath, imagePath)
err = v.copyImageToDev(imagePath)
}
}

if err != nil {
glog.V(4).Infof("Persistent rootfs setup on %q: error: %v", v.dev.HostPath, err)
return nil, nil, err
}

Expand All @@ -102,3 +120,22 @@ func (v *persistentRootVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.Doma
func (v *persistentRootVolume) Teardown() error {
return v.devHandler().Unmap(v.dmName())
}

func syncFiles(paths ...string) error {
// https://www.redhat.com/archives/libguestfs/2012-July/msg00009.html
unix.Sync()
for _, p := range paths {
fd, err := unix.Open(p, unix.O_RDWR|unix.O_SYNC, 0)
if err != nil {
return err
}
if err := unix.Fsync(fd); err != nil {
unix.Close(fd)
return err
}
if err := unix.Close(fd); err != nil {
return err
}
}
return nil
}
56 changes: 30 additions & 26 deletions tests/e2e/block_pv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ var _ = Describe("Block PVs", func() {
devPath string
)

withLoopbackBlockDevice(&virtletNodeName, &devPath)

AfterEach(func() {
if ssh != nil {
ssh.Close()
Expand All @@ -61,32 +59,38 @@ var _ = Describe("Block PVs", func() {
}
})

It("Should be accessible from within the VM", func() {
vm = makeVMWithMountAndSymlinkScript(virtletNodeName, []framework.PVCSpec{
{
Name: "block-pv",
Size: "10M",
NodeName: virtletNodeName,
Block: true,
LocalPath: devPath,
ContainerPath: "/dev/testpvc",
},
}, nil)
ssh = waitSSH(vm)
expectToBeUsableForFilesystem(ssh, "/dev/testpvc")
Context("[Non-root]", func() {
withLoopbackBlockDevice(&virtletNodeName, &devPath, true)
It("Should be accessible from within the VM", func() {
vm = makeVMWithMountAndSymlinkScript(virtletNodeName, []framework.PVCSpec{
{
Name: "block-pv",
Size: "10M",
NodeName: virtletNodeName,
Block: true,
LocalPath: devPath,
ContainerPath: "/dev/testpvc",
},
}, nil)
ssh = waitSSH(vm)
expectToBeUsableForFilesystem(ssh, "/dev/testpvc")
})
})

describePersistentRootfs(func() {
vm = makeVMWithMountAndSymlinkScript(virtletNodeName, []framework.PVCSpec{
{
Name: "block-pv",
Size: "10M",
NodeName: virtletNodeName,
Block: true,
LocalPath: devPath,
ContainerPath: "/",
},
}, nil)
Context("[Root]", func() {
withLoopbackBlockDevice(&virtletNodeName, &devPath, false)
describePersistentRootfs(func() {
vm = makeVMWithMountAndSymlinkScript(virtletNodeName, []framework.PVCSpec{
{
Name: "block-pv",
Size: "10M",
NodeName: virtletNodeName,
Block: true,
LocalPath: devPath,
ContainerPath: "/",
},
}, nil)
})
})
})

Expand Down
45 changes: 31 additions & 14 deletions tests/e2e/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package e2e
import (
"flag"
"fmt"
"os"
"regexp"
"strconv"
"strings"
Expand All @@ -34,7 +35,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const cephContainerName = "ceph_cluster"
const (
cephContainerName = "ceph_cluster"
// avoid having the loop device on top of overlay2/aufs when using k-d-c
loopDeviceTestDir = "/dind/virtlet-e2e-tests"
)

var (
vmImageLocation = flag.String("image", defaultVMImageLocation, "VM image URL (*without http(s)://*")
Expand Down Expand Up @@ -180,35 +185,47 @@ func includeUnsafe() {
}
}

func withLoopbackBlockDevice(virtletNodeName, devPath *string) {
func withLoopbackBlockDevice(virtletNodeName, devPath *string, mkfs bool) {
var nodeExecutor framework.Executor
BeforeAll(func() {
var filename string
BeforeEach(func() {
var err error
*virtletNodeName, err = controller.VirtletNodeName()
Expect(err).NotTo(HaveOccurred())
nodeExecutor, err = controller.DinDNodeExecutor(*virtletNodeName)
Expect(err).NotTo(HaveOccurred())

_, err = framework.RunSimple(nodeExecutor, "dd", "if=/dev/zero", "of=/rawdevtest", "bs=1M", "count=1000")
_, err = framework.RunSimple(nodeExecutor, "mkdir", "-p", loopDeviceTestDir)
Expect(err).NotTo(HaveOccurred())
// We use mkfs.ext3 here because mkfs.ext4 on
// the node may be too new for CirrOS, causing
// errors like this in VM's dmesg:
// [ 1.316395] EXT3-fs (vdb): error: couldn't mount because of unsupported optional features (2c0)
// [ 1.320222] EXT4-fs (vdb): couldn't mount RDWR because of unsupported optional features (400)
// [ 1.339594] EXT3-fs (vdc1): error: couldn't mount because of unsupported optional features (240)
// [ 1.342850] EXT4-fs (vdc1): mounted filesystem with ordered data mode. Opts: (null)
_, err = framework.RunSimple(nodeExecutor, "mkfs.ext3", "/rawdevtest")

filename, err = framework.RunSimple(nodeExecutor, "tempfile", "-d", loopDeviceTestDir, "--prefix", "ve2e-")
Expect(err).NotTo(HaveOccurred())
*devPath, err = framework.RunSimple(nodeExecutor, "losetup", "-f", "/rawdevtest", "--show")

_, err = framework.RunSimple(nodeExecutor, "dd", "if=/dev/zero", "of="+filename, "bs=1M", "count=1000")
Expect(err).NotTo(HaveOccurred())
if mkfs {
// We use mkfs.ext3 here because mkfs.ext4 on
// the node may be too new for CirrOS, causing
// errors like this in VM's dmesg:
// [ 1.316395] EXT3-fs (vdb): error: couldn't mount because of unsupported optional features (2c0)
// [ 1.320222] EXT4-fs (vdb): couldn't mount RDWR because of unsupported optional features (400)
// [ 1.339594] EXT3-fs (vdc1): error: couldn't mount because of unsupported optional features (240)
// [ 1.342850] EXT4-fs (vdc1): mounted filesystem with ordered data mode. Opts: (null)
_, err = framework.RunSimple(nodeExecutor, "mkfs.ext3", filename)
Expect(err).NotTo(HaveOccurred())
}
_, err = framework.RunSimple(nodeExecutor, "sync")
Expect(err).NotTo(HaveOccurred())
*devPath, err = framework.RunSimple(nodeExecutor, "losetup", "-f", filename, "--show")
Expect(err).NotTo(HaveOccurred())
})

AfterAll(func() {
AfterEach(func() {
// The loopback device is detached by itself upon
// success (TODO: check why it happens), so we
// ignore errors here
framework.RunSimple(nodeExecutor, "losetup", "-d", *devPath)
Expect(os.RemoveAll(loopDeviceTestDir)).To(Succeed())
})
}

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/volume_mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var _ = Describe("Container volume mounts", func() {
ssh framework.Executor
)

withLoopbackBlockDevice(&virtletNodeName, &devPath)
withLoopbackBlockDevice(&virtletNodeName, &devPath, true)

AfterEach(func() {
if ssh != nil {
Expand Down