mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-26 07:22:20 +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"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0044fb81f517f480ca3c33675a3af6b4ada77a6faf699a302bc2388c98cacba9"
|
||||
digest = "1:fafdb4aa5b6207f51ec7557818d5f7a534ed44ea4fb31c6f2e8abb01d1627b74"
|
||||
name = "github.com/intel/govmm"
|
||||
packages = ["qemu"]
|
||||
pruneopts = "NUT"
|
||||
revision = "3700c55dd766d37e17af354fb9975dc801619d62"
|
||||
revision = "e969afbec52cf687bbe97b76654c664128cdb04b"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d6e9b99fe0150d4c26d81612676e8d59ad045642e4cbc8646e494b50d4f245ef"
|
||||
|
@ -48,7 +48,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/intel/govmm"
|
||||
revision = "3700c55dd766d37e17af354fb9975dc801619d62"
|
||||
revision = "e969afbec52cf687bbe97b76654c664128cdb04b"
|
||||
|
||||
[[constraint]]
|
||||
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.
|
||||
// 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
|
||||
// the data size of the device.
|
||||
func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64) error {
|
||||
// the data size of the device. pmem is to guarantee the persistence of QEMU writes
|
||||
// to the vNVDIMM backend.
|
||||
func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, size int64, pmem *bool) error {
|
||||
args := map[string]interface{}{
|
||||
"qom-type": "memory-backend-file",
|
||||
"id": "nvdimmbackmem" + id,
|
||||
@ -1439,6 +1440,14 @@ func (q *QMP) ExecuteNVDIMMDeviceAdd(ctx context.Context, id, mempath string, si
|
||||
"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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -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" {
|
||||
@ -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()
|
||||
@ -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
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ini/ini"
|
||||
"golang.org/x/sys/unix"
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
//
|
||||
@ -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
|
||||
}
|
||||
@ -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/";
|
||||
|
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",
|
||||
ID: utils.MakeNameID("drive", device.DeviceInfo.ID, maxDevIDSize),
|
||||
Index: index,
|
||||
Pmem: device.DeviceInfo.Pmem,
|
||||
}
|
||||
|
||||
if fs, ok := device.DeviceInfo.DriverOptions["fstype"]; ok {
|
||||
drive.Format = fs
|
||||
}
|
||||
|
||||
customOptions := device.DeviceInfo.DriverOptions
|
||||
@ -169,6 +174,7 @@ func (device *BlockDevice) Save() persistapi.DeviceState {
|
||||
NvdimmID: drive.NvdimmID,
|
||||
VirtPath: drive.VirtPath,
|
||||
DevNo: drive.DevNo,
|
||||
Pmem: drive.Pmem,
|
||||
}
|
||||
}
|
||||
return ds
|
||||
@ -194,6 +200,7 @@ func (device *BlockDevice) Load(ds persistapi.DeviceState) {
|
||||
NvdimmID: bd.NvdimmID,
|
||||
VirtPath: bd.VirtPath,
|
||||
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
|
||||
func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device, err error) {
|
||||
path, err := config.GetHostPathFunc(devInfo, dm.vhostUserStoreEnabled, dm.vhostUserStorePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devInfo.HostPath = path
|
||||
}
|
||||
devInfo.HostPath = path
|
||||
|
||||
defer func() {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
if isVFIO(path) {
|
||||
if isVFIO(devInfo.HostPath) {
|
||||
return drivers.NewVFIODevice(&devInfo), nil
|
||||
} else if isVhostUserBlk(devInfo) {
|
||||
if devInfo.DriverOptions == nil {
|
||||
@ -134,7 +138,7 @@ func (dm *deviceManager) createDevice(devInfo config.DeviceInfo) (dev api.Device
|
||||
devInfo.DriverOptions["block-driver"] = dm.blockDriver
|
||||
return drivers.NewBlockDevice(&devInfo), nil
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -1114,6 +1114,13 @@ func (k *kataAgent) appendBlockDevice(dev ContainerDevice, c *Container) *grpc.D
|
||||
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{
|
||||
ContainerPath: dev.ContainerPath,
|
||||
}
|
||||
@ -1476,6 +1483,12 @@ func (k *kataAgent) handleDeviceBlockVolume(c *Container, device api.Device) (*g
|
||||
return nil, fmt.Errorf("malformed block drive")
|
||||
}
|
||||
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:
|
||||
vol.Driver = kataBlkCCWDevType
|
||||
vol.Source = blockDrive.DevNo
|
||||
@ -1553,8 +1566,12 @@ func (k *kataAgent) handleBlockVolumes(c *Container) ([]*grpc.Storage, error) {
|
||||
}
|
||||
|
||||
vol.MountPoint = m.Destination
|
||||
vol.Fstype = "bind"
|
||||
vol.Options = []string{"bind"}
|
||||
if vol.Fstype == "" {
|
||||
vol.Fstype = "bind"
|
||||
}
|
||||
if len(vol.Options) == 0 {
|
||||
vol.Options = []string{"bind"}
|
||||
}
|
||||
|
||||
volumeStorages = append(volumeStorages, vol)
|
||||
}
|
||||
|
@ -6,17 +6,16 @@
|
||||
package virtcontainers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
merr "github.com/hashicorp/go-multierror"
|
||||
"github.com/kata-containers/runtime/virtcontainers/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -187,59 +186,6 @@ func getDeviceForPath(path string) (device, error) {
|
||||
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 checkStorageDriver = isDeviceMapper
|
||||
@ -445,7 +391,7 @@ func IsEphemeralStorage(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, fsType, _ := GetDevicePathAndFsType(path); fsType == "tmpfs" {
|
||||
if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType == "tmpfs" {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -460,7 +406,7 @@ func Isk8sHostEmptyDir(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, fsType, _ := GetDevicePathAndFsType(path); fsType != "tmpfs" {
|
||||
if _, fsType, _ := utils.GetDevicePathAndFsType(path); fsType != "tmpfs" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -206,22 +206,6 @@ func TestGetDeviceForPathBindMount(t *testing.T) {
|
||||
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) {
|
||||
assert := assert.New(t)
|
||||
|
||||
|
@ -41,6 +41,10 @@ type BlockDrive struct {
|
||||
|
||||
// DevNo
|
||||
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
|
||||
|
@ -1051,16 +1051,28 @@ func (q *qemu) qmpShutdown() {
|
||||
}
|
||||
|
||||
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
|
||||
file, err := os.Open(drive.File)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
@ -6,10 +6,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -85,3 +88,56 @@ func FindContextID() (*os.File, uint64, error) {
|
||||
vsockFd.Close()
|
||||
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.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