virtcontainers: Implement function to get the pmem DeviceInfo

Implement function to get the pmem `DeviceInfo` from a volume.
`PmemDeviceInfo` return a new `DeviceInfo` object if a volume has a loop device
as backend and the backing file for such loop device contains the PFN signature,
needed to enable DAX in the guest.

Signed-off-by: Julio Montes <julio.montes@intel.com>
This commit is contained in:
Julio Montes 2020-02-28 21:01:57 +00:00
parent 9ff44dba87
commit ee941e5c56
3 changed files with 173 additions and 0 deletions

View File

@ -113,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
@ -169,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

View 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)
}

View 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)
}