mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-28 16:27:50 +00:00
Merge pull request #2515 from devimc/topic/pmem-CSI
Support persistent memory volumes
This commit is contained in:
commit
11c998b6c7
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -412,11 +412,11 @@
|
|||||||
revision = "2f1d1f20f75d5404f53b9edf6b53ed5505508675"
|
revision = "2f1d1f20f75d5404f53b9edf6b53ed5505508675"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:0044fb81f517f480ca3c33675a3af6b4ada77a6faf699a302bc2388c98cacba9"
|
digest = "1:fafdb4aa5b6207f51ec7557818d5f7a534ed44ea4fb31c6f2e8abb01d1627b74"
|
||||||
name = "github.com/intel/govmm"
|
name = "github.com/intel/govmm"
|
||||||
packages = ["qemu"]
|
packages = ["qemu"]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "3700c55dd766d37e17af354fb9975dc801619d62"
|
revision = "e969afbec52cf687bbe97b76654c664128cdb04b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:d6e9b99fe0150d4c26d81612676e8d59ad045642e4cbc8646e494b50d4f245ef"
|
digest = "1:d6e9b99fe0150d4c26d81612676e8d59ad045642e4cbc8646e494b50d4f245ef"
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/intel/govmm"
|
name = "github.com/intel/govmm"
|
||||||
revision = "3700c55dd766d37e17af354fb9975dc801619d62"
|
revision = "e969afbec52cf687bbe97b76654c664128cdb04b"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/kata-containers/agent"
|
name = "github.com/kata-containers/agent"
|
||||||
|
13
vendor/github.com/intel/govmm/qemu/qmp.go
generated
vendored
13
vendor/github.com/intel/govmm/qemu/qmp.go
generated
vendored
@ -1428,8 +1428,9 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string
|
|||||||
// a NVDIMM driver with the device_add command.
|
// a NVDIMM driver with the device_add command.
|
||||||
// id is the id of the device to add. It must be a valid QMP identifier.
|
// id is the id of the device to add. It must be a valid QMP identifier.
|
||||||
// mempath is the path of the device to add, e.g., /dev/rdb0. size is
|
// mempath is the path of the device to add, e.g., /dev/rdb0. size is
|
||||||
// the data size of the device.
|
// the data size of the device. pmem is to guarantee the persistence of QEMU writes
|
||||||
func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64) error {
|
// to the vNVDIMM backend.
|
||||||
|
func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64, pmem *bool) error {
|
||||||
args := map[string]interface{}{
|
args := map[string]interface{}{
|
||||||
"qom-type": "memory-backend-file",
|
"qom-type": "memory-backend-file",
|
||||||
"id": "nvdimmbackmem" + id,
|
"id": "nvdimmbackmem" + id,
|
||||||
@ -1439,6 +1440,14 @@ func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, si
|
|||||||
"share": true,
|
"share": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if q.version.Major > 4 || (q.version.Major == 4 && q.version.Minor >= 1) {
|
||||||
|
if pmem != nil {
|
||||||
|
props := args["props"].(map[string]interface{})
|
||||||
|
props["pmem"] = *pmem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := q.executeCommand(ctx, "object-add", args, nil)
|
err := q.executeCommand(ctx, "object-add", args, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -643,6 +643,11 @@ func filterDevices(c *Container, devices []ContainerDevice) (ret []ContainerDevi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) createBlockDevices() error {
|
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.
|
// iterate all mounts and create block device if it's block based.
|
||||||
for i, m := range c.mounts {
|
for i, m := range c.mounts {
|
||||||
if len(m.BlockDeviceID) > 0 || m.Type != "bind" {
|
if len(m.BlockDeviceID) > 0 || m.Type != "bind" {
|
||||||
@ -657,18 +662,36 @@ func (c *Container) createBlockDevices() error {
|
|||||||
return fmt.Errorf("stat %q failed: %v", m.Source, err)
|
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
|
// 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.
|
// instead of passing this as a shared mount.
|
||||||
if c.checkBlockDeviceSupport() && stat.Mode&unix.S_IFBLK == unix.S_IFBLK {
|
if stat.Mode&unix.S_IFBLK == unix.S_IFBLK {
|
||||||
b, err := c.sandbox.devManager.NewDevice(config.DeviceInfo{
|
di = &config.DeviceInfo{
|
||||||
HostPath: m.Source,
|
HostPath: m.Source,
|
||||||
ContainerPath: m.Destination,
|
ContainerPath: m.Destination,
|
||||||
DevType: "b",
|
DevType: "b",
|
||||||
Major: int64(unix.Major(stat.Rdev)),
|
Major: int64(unix.Major(stat.Rdev)),
|
||||||
Minor: int64(unix.Minor(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 {
|
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()
|
c.mounts[i].BlockDeviceID = b.DeviceID()
|
||||||
@ -1323,7 +1346,7 @@ func (c *Container) hotplugDrive() error {
|
|||||||
c.rootfsSuffix = ""
|
c.rootfsSuffix = ""
|
||||||
}
|
}
|
||||||
// If device mapper device, then fetch the full path of the device
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ini/ini"
|
"github.com/go-ini/ini"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -91,6 +92,8 @@ var SysBusPciDevicesPath = "/sys/bus/pci/devices"
|
|||||||
// SysBusPciSlotsPath is static string of /sys/bus/pci/slots
|
// SysBusPciSlotsPath is static string of /sys/bus/pci/slots
|
||||||
var SysBusPciSlotsPath = "/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.
|
// DeviceInfo is an embedded type that contains device data common to all types of devices.
|
||||||
type DeviceInfo struct {
|
type DeviceInfo struct {
|
||||||
// Hostpath is device path on host
|
// Hostpath is device path on host
|
||||||
@ -110,6 +113,10 @@ type DeviceInfo struct {
|
|||||||
Major int64
|
Major int64
|
||||||
Minor 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 permission bits for the device.
|
||||||
FileMode os.FileMode
|
FileMode os.FileMode
|
||||||
|
|
||||||
@ -166,6 +173,10 @@ type BlockDrive struct {
|
|||||||
|
|
||||||
// ReadOnly sets the device file readonly
|
// ReadOnly sets the device file readonly
|
||||||
ReadOnly bool
|
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
|
// VFIODeviceType indicates VFIO device type
|
||||||
@ -257,29 +268,14 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
|
|||||||
return "", fmt.Errorf("Empty path provided for device")
|
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.
|
// Filter out vhost-user storage devices by device Major numbers.
|
||||||
if vhostUserStoreEnabled && devInfo.DevType == "b" &&
|
if vhostUserStoreEnabled && devInfo.DevType == "b" &&
|
||||||
(devInfo.Major == VhostUserSCSIMajor || devInfo.Major == VhostUserBlkMajor) {
|
(devInfo.Major == VhostUserSCSIMajor || devInfo.Major == VhostUserBlkMajor) {
|
||||||
return getVhostUserHostPath(devInfo, vhostUserStorePath)
|
return getVhostUserHostPath(devInfo, vhostUserStorePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := strconv.FormatInt(devInfo.Major, 10) + ":" + strconv.FormatInt(devInfo.Minor, 10)
|
ueventPath := filepath.Join(getSysDevPath(devInfo), "uevent")
|
||||||
sysDevPath := filepath.Join(SysDevPrefix, pathComp, format, "uevent")
|
if _, err := os.Stat(ueventPath); err != nil {
|
||||||
|
|
||||||
if _, err := os.Stat(sysDevPath); err != nil {
|
|
||||||
// Some devices(eg. /dev/fuse, /dev/cuse) do not always implement sysfs interface under /sys/dev
|
// Some devices(eg. /dev/fuse, /dev/cuse) do not always implement sysfs interface under /sys/dev
|
||||||
// These devices are passed by default by docker.
|
// These devices are passed by default by docker.
|
||||||
//
|
//
|
||||||
@ -293,7 +289,7 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := ini.Load(sysDevPath)
|
content, err := ini.Load(ueventPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -306,6 +302,35 @@ func GetHostPath(devInfo DeviceInfo, vhostUserStoreEnabled bool, vhostUserStoreP
|
|||||||
return filepath.Join("/dev", devName.String()), nil
|
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.
|
// 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
|
// For vhost-user block device like vhost-user-blk or vhost-user-scsi, its
|
||||||
// socket should be under directory "<vhostUserStorePath>/block/sockets/";
|
// socket should be under directory "<vhostUserStorePath>/block/sockets/";
|
||||||
|
73
virtcontainers/device/config/config_test.go
Normal file
73
virtcontainers/device/config/config_test.go
Normal file
@ -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")
|
||||||
|
}
|
116
virtcontainers/device/config/pmem.go
Normal file
116
virtcontainers/device/config/pmem.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/kata-containers/runtime/virtcontainers/utils"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This signature is defined in the linux NVDIMM driver.
|
||||||
|
// devices or backing files with this signature can be used
|
||||||
|
// as pmem (persistent memory) devices in the guest.
|
||||||
|
pfnSignature = "NVDIMM_PFN_INFO"
|
||||||
|
|
||||||
|
// offset in the backing file where the signature should be
|
||||||
|
pfnSignatureOffset = int64(4 * 1024)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pmemLog = logrus.WithField("source", "virtcontainers/device/config")
|
||||||
|
)
|
||||||
|
|
||||||
|
// PmemDeviceInfo returns a DeviceInfo if a loop device
|
||||||
|
// is mounted on source, and the backing file of the loop device
|
||||||
|
// has the PFN signature.
|
||||||
|
func PmemDeviceInfo(source, destination string) (*DeviceInfo, error) {
|
||||||
|
stat := syscall.Stat_t{}
|
||||||
|
err := syscall.Stat(source, &stat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// device object is still incomplete,
|
||||||
|
// but it can be used to fetch the backing file
|
||||||
|
device := &DeviceInfo{
|
||||||
|
ContainerPath: destination,
|
||||||
|
DevType: "b",
|
||||||
|
Major: int64(unix.Major(stat.Dev)),
|
||||||
|
Minor: int64(unix.Minor(stat.Dev)),
|
||||||
|
Pmem: true,
|
||||||
|
DriverOptions: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
pmemLog.WithFields(
|
||||||
|
logrus.Fields{
|
||||||
|
"major": device.Major,
|
||||||
|
"minor": device.Minor,
|
||||||
|
}).Debug("looking for backing file")
|
||||||
|
|
||||||
|
device.HostPath, err = getBackingFile(*device)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pmemLog.WithField("backing-file", device.HostPath).
|
||||||
|
Debug("backing file found: looking for PFN signature")
|
||||||
|
|
||||||
|
if !hasPFNSignature(device.HostPath) {
|
||||||
|
return nil, fmt.Errorf("backing file %v has not PFN signature", device.HostPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, fstype, err := utils.GetDevicePathAndFsType(source)
|
||||||
|
if err != nil {
|
||||||
|
pmemLog.WithError(err).WithField("mount-point", source).Warn("failed to get fstype: using ext4")
|
||||||
|
fstype = "ext4"
|
||||||
|
}
|
||||||
|
|
||||||
|
pmemLog.WithField("fstype", fstype).Debug("filesystem for mount point")
|
||||||
|
device.DriverOptions["fstype"] = fstype
|
||||||
|
|
||||||
|
return device, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if the file/device path has the PFN signature
|
||||||
|
// required to use it as PMEM device and enable DAX.
|
||||||
|
// See [1] to know more about the PFN signature.
|
||||||
|
//
|
||||||
|
// [1] - https://github.com/kata-containers/osbuilder/blob/master/image-builder/nsdax.gpl.c
|
||||||
|
func hasPFNSignature(path string) bool {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
pmemLog.WithError(err).Error("Could not get PFN signature")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
signatureLen := len(pfnSignature)
|
||||||
|
signature := make([]byte, signatureLen)
|
||||||
|
|
||||||
|
l, err := f.ReadAt(signature, pfnSignatureOffset)
|
||||||
|
if err != nil {
|
||||||
|
pmemLog.WithError(err).Debug("Could not read pfn signature")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pmemLog.WithFields(logrus.Fields{
|
||||||
|
"path": path,
|
||||||
|
"signature": string(signature),
|
||||||
|
}).Debug("got signature")
|
||||||
|
|
||||||
|
if l != signatureLen {
|
||||||
|
pmemLog.WithField("read-bytes", l).Debug("Incomplete signature")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return pfnSignature == string(signature)
|
||||||
|
}
|
49
virtcontainers/device/config/pmem_test.go
Normal file
49
virtcontainers/device/config/pmem_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createPFNFile(assert *assert.Assertions, dir string) string {
|
||||||
|
pfnPath := filepath.Join(dir, "pfn")
|
||||||
|
file, err := os.Create(pfnPath)
|
||||||
|
assert.NoError(err)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
l, err := file.WriteAt([]byte(pfnSignature), pfnSignatureOffset)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(len(pfnSignature), l)
|
||||||
|
|
||||||
|
return pfnPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasPFNSignature(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
b := hasPFNSignature("/abc/xyz/123/sw")
|
||||||
|
assert.False(b)
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "pfn")
|
||||||
|
assert.NoError(err)
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
b = hasPFNSignature(f.Name())
|
||||||
|
assert.False(b)
|
||||||
|
|
||||||
|
pfnFile := createPFNFile(assert, os.TempDir())
|
||||||
|
defer os.Remove(pfnFile)
|
||||||
|
|
||||||
|
b = hasPFNSignature(pfnFile)
|
||||||
|
assert.True(b)
|
||||||
|
}
|
@ -65,6 +65,11 @@ func (device *BlockDevice) Attach(devReceiver api.DeviceReceiver) (err error) {
|
|||||||
Format: "raw",
|
Format: "raw",
|
||||||
ID: utils.MakeNameID("drive", device.DeviceInfo.ID, maxDevIDSize),
|
ID: utils.MakeNameID("drive", device.DeviceInfo.ID, maxDevIDSize),
|
||||||
Index: index,
|
Index: index,
|
||||||
|
Pmem: device.DeviceInfo.Pmem,
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, ok := device.DeviceInfo.DriverOptions["fstype"]; ok {
|
||||||
|
drive.Format = fs
|
||||||
}
|
}
|
||||||
|
|
||||||
customOptions := device.DeviceInfo.DriverOptions
|
customOptions := device.DeviceInfo.DriverOptions
|
||||||
@ -169,6 +174,7 @@ func (device *BlockDevice) Save() persistapi.DeviceState {
|
|||||||
NvdimmID: drive.NvdimmID,
|
NvdimmID: drive.NvdimmID,
|
||||||
VirtPath: drive.VirtPath,
|
VirtPath: drive.VirtPath,
|
||||||
DevNo: drive.DevNo,
|
DevNo: drive.DevNo,
|
||||||
|
Pmem: drive.Pmem,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ds
|
return ds
|
||||||
@ -194,6 +200,7 @@ func (device *BlockDevice) Load(ds persistapi.DeviceState) {
|
|||||||
NvdimmID: bd.NvdimmID,
|
NvdimmID: bd.NvdimmID,
|
||||||
VirtPath: bd.VirtPath,
|
VirtPath: bd.VirtPath,
|
||||||
DevNo: bd.DevNo,
|
DevNo: bd.DevNo,
|
||||||
|
Pmem: bd.Pmem,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,11 +98,15 @@ func (dm *deviceManager) findDeviceByMajorMinor(major, minor int64) api.Device {
|
|||||||
|
|
||||||
// createDevice creates one device based on DeviceInfo
|
// createDevice creates one device based on DeviceInfo
|
||||||
func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device, err error) {
|
func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device, err error) {
|
||||||
|
// pmem device may points to block devices or raw files,
|
||||||
|
// do not change its HostPath.
|
||||||
|
if !devInfo.Pmem {
|
||||||
path, err := config.GetHostPathFunc(devInfo, dm.vhostUserStoreEnabled, dm.vhostUserStorePath)
|
path, err := config.GetHostPathFunc(devInfo, dm.vhostUserStoreEnabled, dm.vhostUserStorePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
devInfo.HostPath = path
|
devInfo.HostPath = path
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -119,7 +123,7 @@ func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device
|
|||||||
if devInfo.ID, err = dm.newDeviceID(); err != nil {
|
if devInfo.ID, err = dm.newDeviceID(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if isVFIO(path) {
|
if isVFIO(devInfo.HostPath) {
|
||||||
return drivers.NewVFIODevice(&devInfo), nil
|
return drivers.NewVFIODevice(&devInfo), nil
|
||||||
} else if isVhostUserBlk(devInfo) {
|
} else if isVhostUserBlk(devInfo) {
|
||||||
if devInfo.DriverOptions == nil {
|
if devInfo.DriverOptions == nil {
|
||||||
@ -134,7 +138,7 @@ func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device
|
|||||||
devInfo.DriverOptions["block-driver"] = dm.blockDriver
|
devInfo.DriverOptions["block-driver"] = dm.blockDriver
|
||||||
return drivers.NewBlockDevice(&devInfo), nil
|
return drivers.NewBlockDevice(&devInfo), nil
|
||||||
} else {
|
} else {
|
||||||
deviceLogger().WithField("device", path).Info("Device has not been passed to the container")
|
deviceLogger().WithField("device", devInfo.HostPath).Info("Device has not been passed to the container")
|
||||||
return drivers.NewGenericDevice(&devInfo), nil
|
return drivers.NewGenericDevice(&devInfo), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1114,6 +1114,13 @@ func (k *kataAgent) appendBlockDevice(dev ContainerDevice, c *Container) *grpc.D
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.Pmem {
|
||||||
|
// block drive is a persistent memory device that
|
||||||
|
// was passed as volume (-v) not as device (--device).
|
||||||
|
// It shouldn't be visible in the container
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
kataDevice := &grpc.Device{
|
kataDevice := &grpc.Device{
|
||||||
ContainerPath: dev.ContainerPath,
|
ContainerPath: dev.ContainerPath,
|
||||||
}
|
}
|
||||||
@ -1476,6 +1483,12 @@ func (k *kataAgent) handleDeviceBlockVolume(c *Container, device api.Device) (*g
|
|||||||
return nil, fmt.Errorf("malformed block drive")
|
return nil, fmt.Errorf("malformed block drive")
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
// pmem volumes case
|
||||||
|
case blockDrive.Pmem:
|
||||||
|
vol.Driver = kataNvdimmDevType
|
||||||
|
vol.Source = fmt.Sprintf("/dev/pmem%s", blockDrive.NvdimmID)
|
||||||
|
vol.Fstype = blockDrive.Format
|
||||||
|
vol.Options = []string{"dax"}
|
||||||
case c.sandbox.config.HypervisorConfig.BlockDeviceDriver == config.VirtioBlockCCW:
|
case c.sandbox.config.HypervisorConfig.BlockDeviceDriver == config.VirtioBlockCCW:
|
||||||
vol.Driver = kataBlkCCWDevType
|
vol.Driver = kataBlkCCWDevType
|
||||||
vol.Source = blockDrive.DevNo
|
vol.Source = blockDrive.DevNo
|
||||||
@ -1553,8 +1566,12 @@ func (k *kataAgent) handleBlockVolumes(c *Container) ([]*grpc.Storage, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vol.MountPoint = m.Destination
|
vol.MountPoint = m.Destination
|
||||||
|
if vol.Fstype == "" {
|
||||||
vol.Fstype = "bind"
|
vol.Fstype = "bind"
|
||||||
|
}
|
||||||
|
if len(vol.Options) == 0 {
|
||||||
vol.Options = []string{"bind"}
|
vol.Options = []string{"bind"}
|
||||||
|
}
|
||||||
|
|
||||||
volumeStorages = append(volumeStorages, vol)
|
volumeStorages = append(volumeStorages, vol)
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,16 @@
|
|||||||
package virtcontainers
|
package virtcontainers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
merr "github.com/hashicorp/go-multierror"
|
merr "github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/kata-containers/runtime/virtcontainers/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -187,59 +186,6 @@ func getDeviceForPath(path string) (device, error) {
|
|||||||
return dev, nil
|
return dev, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
procMountsFile = "/proc/mounts"
|
|
||||||
|
|
||||||
fieldsPerLine = 6
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
procDeviceIndex = iota
|
|
||||||
procPathIndex
|
|
||||||
procTypeIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetDevicePathAndFsType gets the device for the mount point and the file system type
|
|
||||||
// of the mount.
|
|
||||||
func GetDevicePathAndFsType(mountPoint string) (devicePath, fsType string, err error) {
|
|
||||||
if mountPoint == "" {
|
|
||||||
err = fmt.Errorf("Mount point cannot be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var file *os.File
|
|
||||||
|
|
||||||
file, err = os.Open(procMountsFile)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
for {
|
|
||||||
var line string
|
|
||||||
|
|
||||||
line, err = reader.ReadString('\n')
|
|
||||||
if err == io.EOF {
|
|
||||||
err = fmt.Errorf("Mount %s not found", mountPoint)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(line)
|
|
||||||
if len(fields) != fieldsPerLine {
|
|
||||||
err = fmt.Errorf("Incorrect no of fields (expected %d, got %d)) :%s", fieldsPerLine, len(fields), line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if mountPoint == fields[procPathIndex] {
|
|
||||||
devicePath = fields[procDeviceIndex]
|
|
||||||
fsType = fields[procTypeIndex]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var blockFormatTemplate = "/sys/dev/block/%d:%d/dm"
|
var blockFormatTemplate = "/sys/dev/block/%d:%d/dm"
|
||||||
|
|
||||||
var checkStorageDriver = isDeviceMapper
|
var checkStorageDriver = isDeviceMapper
|
||||||
@ -445,7 +391,7 @@ func IsEphemeralStorage(path string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, fsType, _ := GetDevicePathAndFsType(path); fsType == "tmpfs" {
|
if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType == "tmpfs" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,7 +406,7 @@ func Isk8sHostEmptyDir(path string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, fsType, _ := GetDevicePathAndFsType(path); fsType != "tmpfs" {
|
if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType != "tmpfs" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -206,22 +206,6 @@ func TestGetDeviceForPathBindMount(t *testing.T) {
|
|||||||
assert.Equal(sourceDev, destDev)
|
assert.Equal(sourceDev, destDev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetDevicePathAndFsTypeEmptyMount(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
_, _, err := GetDevicePathAndFsType("")
|
|
||||||
assert.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDevicePathAndFsTypeSuccessful(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
path, fstype, err := GetDevicePathAndFsType("/proc")
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
assert.Equal(path, "proc")
|
|
||||||
assert.Equal(fstype, "proc")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsDeviceMapper(t *testing.T) {
|
func TestIsDeviceMapper(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
@ -41,6 +41,10 @@ type BlockDrive struct {
|
|||||||
|
|
||||||
// DevNo
|
// DevNo
|
||||||
DevNo string
|
DevNo string
|
||||||
|
|
||||||
|
// Pmem enabled persistent memory. Use File as backing file
|
||||||
|
// for a nvdimm device in the guest.
|
||||||
|
Pmem bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// VFIODev represents a VFIO drive used for hotplugging
|
// VFIODev represents a VFIO drive used for hotplugging
|
||||||
|
@ -1051,16 +1051,28 @@ func (q *qemu) qmpShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *qemu) hotplugAddBlockDevice(drive *config.BlockDrive, op operation, devID string) (err error) {
|
func (q *qemu) hotplugAddBlockDevice(drive *config.BlockDrive, op operation, devID string) (err error) {
|
||||||
if q.config.BlockDeviceDriver == config.Nvdimm {
|
// drive can be a pmem device, in which case it's used as backing file for a nvdimm device
|
||||||
|
if q.config.BlockDeviceDriver == config.Nvdimm || drive.Pmem {
|
||||||
var blocksize int64
|
var blocksize int64
|
||||||
file, err := os.Open(drive.File)
|
file, err := os.Open(drive.File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&blocksize))); err != 0 {
|
defer file.Close()
|
||||||
|
|
||||||
|
st, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get information from nvdimm device %v: %v", drive.File, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// regular files do not support syscall BLKGETSIZE64
|
||||||
|
if st.Mode().IsRegular() {
|
||||||
|
blocksize = st.Size()
|
||||||
|
} else if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&blocksize))); err != 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = q.qmpMonitorCh.qmp.ExecuteNVDIMMDeviceAdd(q.qmpMonitorCh.ctx, drive.ID, drive.File, blocksize); err != nil {
|
|
||||||
|
if err = q.qmpMonitorCh.qmp.ExecuteNVDIMMDeviceAdd(q.qmpMonitorCh.ctx, drive.ID, drive.File, blocksize, &drive.Pmem); err != nil {
|
||||||
q.Logger().WithError(err).Errorf("Failed to add NVDIMM device %s", drive.File)
|
q.Logger().WithError(err).Errorf("Failed to add NVDIMM device %s", drive.File)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -85,3 +88,56 @@ func FindContextID() (*os.File, uint64, error) {
|
|||||||
vsockFd.Close()
|
vsockFd.Close()
|
||||||
return nil, 0, fmt.Errorf("Could not get a unique context ID for the vsock : %s", err)
|
return nil, 0, fmt.Errorf("Could not get a unique context ID for the vsock : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
procMountsFile = "/proc/mounts"
|
||||||
|
|
||||||
|
fieldsPerLine = 6
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
procDeviceIndex = iota
|
||||||
|
procPathIndex
|
||||||
|
procTypeIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDevicePathAndFsType gets the device for the mount point and the file system type
|
||||||
|
// of the mount.
|
||||||
|
func GetDevicePathAndFsType(mountPoint string) (devicePath, fsType string, err error) {
|
||||||
|
if mountPoint == "" {
|
||||||
|
err = fmt.Errorf("Mount point cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var file *os.File
|
||||||
|
|
||||||
|
file, err = os.Open(procMountsFile)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
for {
|
||||||
|
var line string
|
||||||
|
|
||||||
|
line, err = reader.ReadString('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
err = fmt.Errorf("Mount %s not found", mountPoint)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) != fieldsPerLine {
|
||||||
|
err = fmt.Errorf("Incorrect no of fields (expected %d, got %d)) :%s", fieldsPerLine, len(fields), line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mountPoint == fields[procPathIndex] {
|
||||||
|
devicePath = fields[procDeviceIndex]
|
||||||
|
fsType = fields[procTypeIndex]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -33,3 +33,19 @@ func TestFindContextID(t *testing.T) {
|
|||||||
assert.Zero(cid)
|
assert.Zero(cid)
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDevicePathAndFsTypeEmptyMount(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
_, _, err := GetDevicePathAndFsType("")
|
||||||
|
assert.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDevicePathAndFsTypeSuccessful(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
path, fstype, err := GetDevicePathAndFsType("/proc")
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
assert.Equal(path, "proc")
|
||||||
|
assert.Equal(fstype, "proc")
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user