Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Support persistent memory volumes #2515

Merged
merged 8 commits into from
Mar 26, 2020
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
4 changes: 2 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

[[constraint]]
name = "github.com/intel/govmm"
revision = "3700c55dd766d37e17af354fb9975dc801619d62"
revision = "e969afbec52cf687bbe97b76654c664128cdb04b"

[[constraint]]
name = "github.com/kata-containers/agent"
Expand Down
13 changes: 11 additions & 2 deletions vendor/github.com/intel/govmm/qemu/qmp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 28 additions & 5 deletions virtcontainers/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,11 @@ func filterDevices(c *Container, devices []ContainerDevice) (ret []ContainerDevi
}

func (c *Container) createBlockDevices() error {
if !c.checkBlockDeviceSupport() {
c.Logger().Warn("Block device not supported")
return nil
}

// iterate all mounts and create block device if it's block based.
for i, m := range c.mounts {
if len(m.BlockDeviceID) > 0 || m.Type != "bind" {
Expand All @@ -657,18 +662,36 @@ func (c *Container) createBlockDevices() error {
return fmt.Errorf("stat %q failed: %v", m.Source, err)
}

var di *config.DeviceInfo
var err error

// Check if mount is a block device file. If it is, the block device will be attached to the host
// instead of passing this as a shared mount.
if c.checkBlockDeviceSupport() && stat.Mode&unix.S_IFBLK == unix.S_IFBLK {
b, err := c.sandbox.devManager.NewDevice(config.DeviceInfo{
if stat.Mode&unix.S_IFBLK == unix.S_IFBLK {
di = &config.DeviceInfo{
HostPath: m.Source,
ContainerPath: m.Destination,
DevType: "b",
Major: int64(unix.Major(stat.Rdev)),
Minor: int64(unix.Minor(stat.Rdev)),
})
}
// check whether source can be used as a pmem device
} else if di, err = config.PmemDeviceInfo(m.Source, m.Destination); err != nil {
c.Logger().WithError(err).
WithField("mount-source", m.Source).
Debug("no loop device")
}

if err == nil && di != nil {
b, err := c.sandbox.devManager.NewDevice(*di)

if err != nil {
return fmt.Errorf("device manager failed to create new device for %q: %v", m.Source, err)
// Do not return an error, try to create
// devices for other mounts
c.Logger().WithError(err).WithField("mount-source", m.Source).
Error("device manager failed to create new device")
continue

}

c.mounts[i].BlockDeviceID = b.DeviceID()
Expand Down Expand Up @@ -1323,7 +1346,7 @@ func (c *Container) hotplugDrive() error {
c.rootfsSuffix = ""
}
// If device mapper device, then fetch the full path of the device
devicePath, fsType, err = GetDevicePathAndFsType(dev.mountPoint)
devicePath, fsType, err = utils.GetDevicePathAndFsType(dev.mountPoint)
if err != nil {
return err
}
Expand Down
61 changes: 43 additions & 18 deletions virtcontainers/device/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"

"github.com/go-ini/ini"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -91,6 +92,8 @@ var SysBusPciDevicesPath = "/sys/bus/pci/devices"
// SysBusPciSlotsPath is static string of /sys/bus/pci/slots
var SysBusPciSlotsPath = "/sys/bus/pci/slots"

var getSysDevPath = getSysDevPathImpl

// DeviceInfo is an embedded type that contains device data common to all types of devices.
type DeviceInfo struct {
// Hostpath is device path on host
Expand All @@ -110,6 +113,10 @@ type DeviceInfo struct {
Major int64
Minor int64

// Pmem enabled persistent memory. Use HostPath as backing file
// for a nvdimm device in the guest.
Pmem bool

// FileMode permission bits for the device.
FileMode os.FileMode

Expand Down Expand Up @@ -166,6 +173,10 @@ type BlockDrive struct {

// ReadOnly sets the device file readonly
ReadOnly bool

// Pmem enables persistent memory. Use File as backing file
// for a nvdimm device in the guest
Pmem bool
}

// VFIODeviceType indicates VFIO device type
Expand Down Expand Up @@ -257,29 +268,14 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
return "", fmt.Errorf("Empty path provided for device")
}

var pathComp string

switch devInfo.DevType {
case "c", "u":
pathComp = "char"
case "b":
pathComp = "block"
default:
// Unsupported device types. Return nil error to ignore devices
// that cannot be handled currently.
return "", nil
}

// Filter out vhost-user storage devices by device Major numbers.
if vhostUserStoreEnabled && devInfo.DevType == "b" &&
(devInfo.Major == VhostUserSCSIMajor || devInfo.Major == VhostUserBlkMajor) {
return getVhostUserHostPath(devInfo, vhostUserStorePath)
}

format := strconv.FormatInt(devInfo.Major, 10) + ":" + strconv.FormatInt(devInfo.Minor, 10)
sysDevPath := filepath.Join(SysDevPrefix, pathComp, format, "uevent")

if _, err := os.Stat(sysDevPath); err != nil {
ueventPath := filepath.Join(getSysDevPath(devInfo), "uevent")
if _, err := os.Stat(ueventPath); err != nil {
// Some devices(eg. /dev/fuse, /dev/cuse) do not always implement sysfs interface under /sys/dev
// These devices are passed by default by docker.
//
Expand All @@ -293,7 +289,7 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
return "", err
}

content, err := ini.Load(sysDevPath)
content, err := ini.Load(ueventPath)
if err != nil {
return "", err
}
Expand All @@ -306,6 +302,35 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
return filepath.Join("/dev", devName.String()), nil
}

// getBackingFile is used to fetch the backing file for the device.
func getBackingFile(devInfo DeviceInfo) (string, error) {
backingFilePath := filepath.Join(getSysDevPath(devInfo), "loop", "backing_file")
data, err := ioutil.ReadFile(backingFilePath)
if err != nil {
return "", err
}

return strings.TrimSpace(string(data)), nil
}

func getSysDevPathImpl(devInfo DeviceInfo) string {
var pathComp string

switch devInfo.DevType {
case "c", "u":
pathComp = "char"
case "b":
pathComp = "block"
default:
// Unsupported device types. Return nil error to ignore devices
// that cannot be handled currently.
return ""
}

format := strconv.FormatInt(devInfo.Major, 10) + ":" + strconv.FormatInt(devInfo.Minor, 10)
return filepath.Join(SysDevPrefix, pathComp, format)
}

// getVhostUserHostPath is used to fetch host path for the vhost-user device.
// For vhost-user block device like vhost-user-blk or vhost-user-scsi, its
// socket should be under directory "<vhostUserStorePath>/block/sockets/";
Expand Down
73 changes: 73 additions & 0 deletions virtcontainers/device/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//

package config

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetBackingFile(t *testing.T) {
assert := assert.New(t)

dir, err := ioutil.TempDir("", "backing")
assert.NoError(err)
defer os.RemoveAll(dir)

orgGetSysDevPath := getSysDevPath
getSysDevPath = func(info DeviceInfo) string {
return dir
}
defer func() { getSysDevPath = orgGetSysDevPath }()

info := DeviceInfo{}
path, err := getBackingFile(info)
assert.Error(err)
assert.Empty(path)

loopDir := filepath.Join(dir, "loop")
err = os.Mkdir(loopDir, os.FileMode(0755))
assert.NoError(err)

backingFile := "/fake-img"

err = ioutil.WriteFile(filepath.Join(loopDir, "backing_file"), []byte(backingFile), os.FileMode(0755))
assert.NoError(err)

path, err = getBackingFile(info)
assert.NoError(err)
assert.Equal(backingFile, path)
}

func TestGetSysDevPathImpl(t *testing.T) {
assert := assert.New(t)

info := DeviceInfo{
DevType: "",
Major: 127,
Minor: 0,
}

path := getSysDevPathImpl(info)
assert.Empty(path)

expectedFormat := fmt.Sprintf("%d:%d", info.Major, info.Minor)

info.DevType = "c"
path = getSysDevPathImpl(info)
assert.Contains(path, expectedFormat)
assert.Contains(path, "char")

info.DevType = "b"
path = getSysDevPathImpl(info)
assert.Contains(path, expectedFormat)
assert.Contains(path, "block")
}
Loading