util: move kernel version functions to pkg/util/kernel

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos 2025-03-03 18:33:55 +01:00
parent 6ea73af34c
commit 9bd6cbf5ca
12 changed files with 323 additions and 266 deletions

View File

@ -23,6 +23,8 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/ceph/ceph-csi/pkg/util/kernel"
"github.com/ceph/ceph-csi/internal/cephfs" "github.com/ceph/ceph-csi/internal/cephfs"
"github.com/ceph/ceph-csi/internal/controller" "github.com/ceph/ceph-csi/internal/controller"
"github.com/ceph/ceph-csi/internal/controller/persistentvolume" "github.com/ceph/ceph-csi/internal/controller/persistentvolume"
@ -191,7 +193,7 @@ func printVersion() {
fmt.Println("Go Version:", runtime.Version()) fmt.Println("Go Version:", runtime.Version())
fmt.Println("Compiler:", runtime.Compiler) fmt.Println("Compiler:", runtime.Compiler)
fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
if kv, err := util.GetKernelVersion(); err == nil { if kv, err := kernel.GetKernelVersion(); err == nil {
fmt.Println("Kernel:", kv) fmt.Println("Kernel:", kv)
} }
} }

View File

@ -23,7 +23,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/pkg/util/crypto"
"github.com/ceph/ceph-csi/pkg/util/kernel"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -357,7 +358,7 @@ var _ = Describe("RBD", func() {
framework.Failf("failed to get the kernel version: %v", err) framework.Failf("failed to get the kernel version: %v", err)
} }
// default io-timeout=0, needs kernel >= 5.4 // default io-timeout=0, needs kernel >= 5.4
if !util.CheckKernelSupport(kernelRelease, nbdZeroIOtimeoutSupport) { if !kernel.CheckKernelSupport(kernelRelease, nbdZeroIOtimeoutSupport) {
nbdMapOptions = "nbd:debug-rbd=20,io-timeout=330" nbdMapOptions = "nbd:debug-rbd=20,io-timeout=330"
} }
@ -1223,7 +1224,7 @@ var _ = Describe("RBD", func() {
return return
} }
if util.CheckKernelSupport(kernelRelease, nbdResizeSupport) { if kernel.CheckKernelSupport(kernelRelease, nbdResizeSupport) {
err := deleteResource(rbdExamplePath + "storageclass.yaml") err := deleteResource(rbdExamplePath + "storageclass.yaml")
if err != nil { if err != nil {
framework.Failf("failed to delete storageclass: %v", err) framework.Failf("failed to delete storageclass: %v", err)
@ -1273,7 +1274,7 @@ var _ = Describe("RBD", func() {
By("create PVC with layering,fast-diff image-features and bind it to an app", By("create PVC with layering,fast-diff image-features and bind it to an app",
func() { func() {
if util.CheckKernelSupport(kernelRelease, fastDiffSupport) { if kernel.CheckKernelSupport(kernelRelease, fastDiffSupport) {
err := deleteResource(rbdExamplePath + "storageclass.yaml") err := deleteResource(rbdExamplePath + "storageclass.yaml")
if err != nil { if err != nil {
framework.Failf("failed to delete storageclass: %v", err) framework.Failf("failed to delete storageclass: %v", err)
@ -1340,7 +1341,7 @@ var _ = Describe("RBD", func() {
validateRBDImageCount(f, 1, defaultRBDPool) validateRBDImageCount(f, 1, defaultRBDPool)
validateOmapCount(f, 1, rbdType, defaultRBDPool, volumesType) validateOmapCount(f, 1, rbdType, defaultRBDPool, volumesType)
if util.CheckKernelSupport(kernelRelease, deepFlattenSupport) { if kernel.CheckKernelSupport(kernelRelease, deepFlattenSupport) {
app, aErr := loadApp(appPath) app, aErr := loadApp(appPath)
if aErr != nil { if aErr != nil {
framework.Failf("failed to load application: %v", aErr) framework.Failf("failed to load application: %v", aErr)
@ -1401,7 +1402,7 @@ var _ = Describe("RBD", func() {
// checking the minimal kernel version for fast-diff as its // checking the minimal kernel version for fast-diff as its
// higher kernel version than other default image features. // higher kernel version than other default image features.
if util.CheckKernelSupport(kernelRelease, fastDiffSupport) { if kernel.CheckKernelSupport(kernelRelease, fastDiffSupport) {
app, aErr := loadApp(appPath) app, aErr := loadApp(appPath)
if aErr != nil { if aErr != nil {
framework.Failf("failed to load application: %v", aErr) framework.Failf("failed to load application: %v", aErr)
@ -1436,7 +1437,7 @@ var _ = Describe("RBD", func() {
return return
} }
if util.CheckKernelSupport(kernelRelease, fastDiffSupport) { if kernel.CheckKernelSupport(kernelRelease, fastDiffSupport) {
err := deleteResource(rbdExamplePath + "storageclass.yaml") err := deleteResource(rbdExamplePath + "storageclass.yaml")
if err != nil { if err != nil {
framework.Failf("failed to delete storageclass: %v", err) framework.Failf("failed to delete storageclass: %v", err)
@ -1968,7 +1969,7 @@ var _ = Describe("RBD", func() {
// Writes on kernel < 5.4 are failing due to a bug in NBD driver, // Writes on kernel < 5.4 are failing due to a bug in NBD driver,
// NBD zero cmd timeout handling is fixed with kernel >= 5.4 // NBD zero cmd timeout handling is fixed with kernel >= 5.4
// see https://github.com/ceph/ceph-csi/issues/2204#issuecomment-930941047 // see https://github.com/ceph/ceph-csi/issues/2204#issuecomment-930941047
if util.CheckKernelSupport(kernelRelease, nbdZeroIOtimeoutSupport) { if kernel.CheckKernelSupport(kernelRelease, nbdZeroIOtimeoutSupport) {
filePath := app.Spec.Containers[0].VolumeMounts[0].MountPath + "/test" filePath := app.Spec.Containers[0].VolumeMounts[0].MountPath + "/test"
_, stdErr, err = execCommandInPod( _, stdErr, err = execCommandInPod(
f, f,

View File

@ -25,7 +25,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/pkg/util/kernel"
snapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1" snapapi "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -37,7 +37,7 @@ import (
) )
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
var nbdResizeSupport = []util.KernelVersion{ var nbdResizeSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 3, PatchLevel: 3,
@ -49,7 +49,7 @@ var nbdResizeSupport = []util.KernelVersion{
} }
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
var fastDiffSupport = []util.KernelVersion{ var fastDiffSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 3, PatchLevel: 3,
@ -61,7 +61,7 @@ var fastDiffSupport = []util.KernelVersion{
} }
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
var deepFlattenSupport = []util.KernelVersion{ var deepFlattenSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 1, PatchLevel: 1,
@ -76,7 +76,7 @@ var deepFlattenSupport = []util.KernelVersion{
// www.mail-archive.com/linux-block@vger.kernel.org/msg38060.html // www.mail-archive.com/linux-block@vger.kernel.org/msg38060.html
// //
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
var nbdZeroIOtimeoutSupport = []util.KernelVersion{ var nbdZeroIOtimeoutSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 4, PatchLevel: 4,

View File

@ -23,6 +23,8 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/ceph/ceph-csi/pkg/util/kernel"
"github.com/ceph/ceph-csi/internal/cephfs/store" "github.com/ceph/ceph-csi/internal/cephfs/store"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util"
"github.com/ceph/ceph-csi/internal/util/log" "github.com/ceph/ceph-csi/internal/util/log"
@ -32,7 +34,7 @@ var (
availableMounters []string availableMounters []string
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
quotaSupport = []util.KernelVersion{ quotaSupport = []kernel.KernelVersion{
{ {
Version: 4, Version: 4,
PatchLevel: 17, PatchLevel: 17,
@ -70,12 +72,12 @@ func LoadAvailableMounters(conf *util.Config) error {
log.ErrorLogMsg("failed to run mount.ceph %v", err) log.ErrorLogMsg("failed to run mount.ceph %v", err)
} else { } else {
// fetch the current running kernel info // fetch the current running kernel info
release, kvErr := util.GetKernelVersion() release, kvErr := kernel.GetKernelVersion()
if kvErr != nil { if kvErr != nil {
return kvErr return kvErr
} }
if conf.ForceKernelCephFS || util.CheckKernelSupport(release, quotaSupport) { if conf.ForceKernelCephFS || kernel.CheckKernelSupport(release, quotaSupport) {
log.DefaultLog("loaded mounter: %s", volumeMounterKernel) log.DefaultLog("loaded mounter: %s", volumeMounterKernel)
availableMounters = append(availableMounters, volumeMounterKernel) availableMounters = append(availableMounters, volumeMounterKernel)
} else { } else {

View File

@ -24,6 +24,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/ceph/ceph-csi/pkg/util/kernel"
csicommon "github.com/ceph/ceph-csi/internal/csi-common" csicommon "github.com/ceph/ceph-csi/internal/csi-common"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util"
"github.com/ceph/ceph-csi/internal/util/file" "github.com/ceph/ceph-csi/internal/util/file"
@ -88,7 +90,7 @@ var (
// deepFlattenSupport holds the list of kernel which support mapping rbd // deepFlattenSupport holds the list of kernel which support mapping rbd
// image with deep-flatten image feature // image with deep-flatten image feature
//nolint:mnd // numbers specify Kernel versions. //nolint:mnd // numbers specify Kernel versions.
deepFlattenSupport = []util.KernelVersion{ deepFlattenSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 1, PatchLevel: 1,
@ -587,12 +589,12 @@ func flattenImageBeforeMapping(
if kernelRelease == "" { if kernelRelease == "" {
// fetch the current running kernel info // fetch the current running kernel info
kernelRelease, err = util.GetKernelVersion() kernelRelease, err = kernel.GetKernelVersion()
if err != nil { if err != nil {
return err return err
} }
} }
if !util.CheckKernelSupport(kernelRelease, deepFlattenSupport) && !skipForceFlatten { if !kernel.CheckKernelSupport(kernelRelease, deepFlattenSupport) && !skipForceFlatten {
feature, err = volOptions.checkImageChainHasFeature(ctx, librbd.FeatureDeepFlatten) feature, err = volOptions.checkImageChainHasFeature(ctx, librbd.FeatureDeepFlatten)
if err != nil { if err != nil {
return err return err

View File

@ -25,6 +25,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/ceph/ceph-csi/pkg/util/kernel"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util"
"github.com/ceph/ceph-csi/internal/util/log" "github.com/ceph/ceph-csi/internal/util/log"
@ -71,7 +73,7 @@ var (
hasNBD = true hasNBD = true
hasNBDCookieSupport = false hasNBDCookieSupport = false
kernelCookieSupport = []util.KernelVersion{ kernelCookieSupport = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 14, PatchLevel: 14,
@ -243,13 +245,13 @@ func SetRbdNbdToolFeatures() {
log.DefaultLog("nbd module loaded") log.DefaultLog("nbd module loaded")
// fetch the current running kernel info // fetch the current running kernel info
release, err := util.GetKernelVersion() release, err := kernel.GetKernelVersion()
if err != nil { if err != nil {
log.WarningLogMsg("fetching current kernel version failed (%v)", err) log.WarningLogMsg("fetching current kernel version failed (%v)", err)
return return
} }
if !util.CheckKernelSupport(release, kernelCookieSupport) { if !kernel.CheckKernelSupport(release, kernelCookieSupport) {
log.WarningLogMsg("kernel version %q doesn't support cookie feature", release) log.WarningLogMsg("kernel version %q doesn't support cookie feature", release)
return return

View File

@ -29,6 +29,7 @@ import (
"time" "time"
"github.com/ceph/ceph-csi/pkg/util/crypto" "github.com/ceph/ceph-csi/pkg/util/crypto"
"github.com/ceph/ceph-csi/pkg/util/kernel"
"github.com/ceph/ceph-csi/internal/rbd/types" "github.com/ceph/ceph-csi/internal/rbd/types"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util"
@ -245,28 +246,28 @@ var (
}, },
} }
krbdLayeringSupport = []util.KernelVersion{ krbdLayeringSupport = []kernel.KernelVersion{
{ {
Version: 3, Version: 3,
PatchLevel: 8, PatchLevel: 8,
SubLevel: 0, SubLevel: 0,
}, },
} }
krbdStripingV2Support = []util.KernelVersion{ krbdStripingV2Support = []kernel.KernelVersion{
{ {
Version: 3, Version: 3,
PatchLevel: 10, PatchLevel: 10,
SubLevel: 0, SubLevel: 0,
}, },
} }
krbdExclusiveLockSupport = []util.KernelVersion{ krbdExclusiveLockSupport = []kernel.KernelVersion{
{ {
Version: 4, Version: 4,
PatchLevel: 9, PatchLevel: 9,
SubLevel: 0, SubLevel: 0,
}, },
} }
krbdDataPoolSupport = []util.KernelVersion{ krbdDataPoolSupport = []kernel.KernelVersion{
{ {
Version: 4, Version: 4,
PatchLevel: 11, PatchLevel: 11,
@ -279,19 +280,19 @@ var (
// Minimum kernel version should be 3.8, else it will return error. // Minimum kernel version should be 3.8, else it will return error.
func prepareKrbdFeatureAttrs() (uint64, error) { func prepareKrbdFeatureAttrs() (uint64, error) {
// fetch the current running kernel info // fetch the current running kernel info
release, err := util.GetKernelVersion() release, err := kernel.GetKernelVersion()
if err != nil { if err != nil {
return 0, fmt.Errorf("fetching current kernel version failed: %w", err) return 0, fmt.Errorf("fetching current kernel version failed: %w", err)
} }
switch { switch {
case util.CheckKernelSupport(release, krbdDataPoolSupport): case kernel.CheckKernelSupport(release, krbdDataPoolSupport):
return librbd.FeatureDataPool, nil return librbd.FeatureDataPool, nil
case util.CheckKernelSupport(release, krbdExclusiveLockSupport): case kernel.CheckKernelSupport(release, krbdExclusiveLockSupport):
return librbd.FeatureExclusiveLock, nil return librbd.FeatureExclusiveLock, nil
case util.CheckKernelSupport(release, krbdStripingV2Support): case kernel.CheckKernelSupport(release, krbdStripingV2Support):
return librbd.FeatureStripingV2, nil return librbd.FeatureStripingV2, nil
case util.CheckKernelSupport(release, krbdLayeringSupport): case kernel.CheckKernelSupport(release, krbdLayeringSupport):
return librbd.FeatureLayering, nil return librbd.FeatureLayering, nil
} }
log.ErrorLogMsg("kernel version is too old: %q", release) log.ErrorLogMsg("kernel version is too old: %q", release)

View File

@ -35,6 +35,8 @@ import (
"github.com/pkg/xattr" "github.com/pkg/xattr"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"github.com/ceph/ceph-csi/pkg/util/kernel"
"github.com/ceph/ceph-csi/internal/kms" "github.com/ceph/ceph-csi/internal/kms"
"github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util"
"github.com/ceph/ceph-csi/internal/util/log" "github.com/ceph/ceph-csi/internal/util/log"
@ -47,7 +49,7 @@ const (
encryptionPassphraseSize = 64 encryptionPassphraseSize = 64
) )
var policyV2Support = []util.KernelVersion{ var policyV2Support = []kernel.KernelVersion{
{ {
Version: 5, Version: 5,
PatchLevel: 4, PatchLevel: 4,
@ -327,13 +329,13 @@ func IsDirectoryUnlocked(directoryPath, filesystem string) error {
func getBestPolicyVersion() (int64, error) { func getBestPolicyVersion() (int64, error) {
// fetch the current running kernel info // fetch the current running kernel info
release, err := util.GetKernelVersion() release, err := kernel.GetKernelVersion()
if err != nil { if err != nil {
return 0, fmt.Errorf("fetching current kernel version failed: %w", err) return 0, fmt.Errorf("fetching current kernel version failed: %w", err)
} }
switch { switch {
case util.CheckKernelSupport(release, policyV2Support): case kernel.CheckKernelSupport(release, policyV2Support):
return 2, nil return 2, nil
default: default:
return 1, nil return 1, nil

View File

@ -28,9 +28,7 @@ import (
"time" "time"
"github.com/ceph/ceph-csi/internal/util/k8s" "github.com/ceph/ceph-csi/internal/util/k8s"
"github.com/ceph/ceph-csi/internal/util/log"
"golang.org/x/sys/unix"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cloud-provider/volume/helpers" "k8s.io/cloud-provider/volume/helpers"
mount "k8s.io/mount-utils" mount "k8s.io/mount-utils"
@ -177,112 +175,6 @@ func ValidateDriverName(driverName string) error {
return err return err
} }
// GetKernelVersion returns the version of the running Unix (like) system from the
// 'utsname' structs 'release' component.
func GetKernelVersion() (string, error) {
utsname := unix.Utsname{}
if err := unix.Uname(&utsname); err != nil {
return "", err
}
return strings.TrimRight(string(utsname.Release[:]), "\x00"), nil
}
// KernelVersion holds kernel related information.
type KernelVersion struct {
Version int
PatchLevel int
SubLevel int
ExtraVersion int // prefix of the part after the first "-"
Distribution string // component of full extraversion
Backport bool // backport have a fixed version/patchlevel/sublevel
}
// parseKernelRelease parses a kernel release version string into:
// version, patch version, sub version and extra version.
func parseKernelRelease(release string) (int, int, int, int, error) {
version := 0
patchlevel := 0
minVersions := 2
extra := ""
n, err := fmt.Sscanf(release, "%d.%d%s", &version, &patchlevel, &extra)
if n < minVersions && err != nil {
return 0, 0, 0, 0, fmt.Errorf("failed to parse version and patchlevel from %s: %w", release, err)
}
sublevel := 0
extraversion := 0
if n > minVersions {
n, err = fmt.Sscanf(extra, ".%d%s", &sublevel, &extra)
if err != nil && n == 0 && extra != "" && extra[0] != '-' && extra[0] == '.' {
return 0, 0, 0, 0, fmt.Errorf("failed to parse subversion from %s: %w", release, err)
}
extra = strings.TrimPrefix(extra, "-")
// ignore errors, 1st component of extraversion does not need to be an int
_, err = fmt.Sscanf(extra, "%d", &extraversion)
if err != nil {
// "go lint" wants err to be checked...
extraversion = 0
}
}
return version, patchlevel, sublevel, extraversion, nil
}
// CheckKernelSupport checks the running kernel and comparing it to known
// versions that have support for required features . Distributors of
// enterprise Linux have backport quota support to previous versions. This
// function checks if the running kernel is one of the versions that have the
// feature/fixes backport.
//
// `uname -r` (or Uname().Utsname.Release has a format like 1.2.3-rc.vendor
// This can be slit up in the following components: - version (1) - patchlevel
// (2) - sublevel (3) - optional, defaults to 0 - extraversion (rc) - optional,
// matching integers only - distribution (.vendor) - optional, match against
// whole `uname -r` string
//
// For matching multiple versions, the kernelSupport type contains a backport
// bool, which will cause matching
// version+patchlevel+sublevel+(>=extraversion)+(~distribution)
//
// In case the backport bool is false, a simple check for higher versions than
// version+patchlevel+sublevel is done.
func CheckKernelSupport(release string, supportedVersions []KernelVersion) bool {
version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release)
if err != nil {
log.ErrorLogMsg("%v", err)
return false
}
// compare running kernel against known versions
for _, kernel := range supportedVersions {
if !kernel.Backport {
// deal with the default case(s), find >= match for version, patchlevel, sublevel
if version > kernel.Version || (version == kernel.Version && patchlevel > kernel.PatchLevel) ||
(version == kernel.Version && patchlevel == kernel.PatchLevel && sublevel >= kernel.SubLevel) {
return true
}
} else {
// specific backport, match distribution initially
if !strings.Contains(release, kernel.Distribution) {
continue
}
// strict match version, patchlevel, sublevel, and >= match extraversion
if version == kernel.Version && patchlevel == kernel.PatchLevel &&
sublevel == kernel.SubLevel && extraversion >= kernel.ExtraVersion {
return true
}
}
}
log.WarningLogMsg("kernel %s does not support required features", release)
return false
}
// GenerateVolID generates a volume ID based on passed in parameters and version, to be returned // GenerateVolID generates a volume ID based on passed in parameters and version, to be returned
// to the CO system. // to the CO system.
func GenerateVolID( func GenerateVolID(

View File

@ -16,7 +16,6 @@ limitations under the License.
package util package util
import ( import (
"strings"
"testing" "testing"
) )
@ -146,20 +145,6 @@ func TestRoundOffVolSize(t *testing.T) {
} }
} }
func TestGetKernelVersion(t *testing.T) {
t.Parallel()
version, err := GetKernelVersion()
if err != nil {
t.Errorf("failed to get kernel version: %s", err)
}
if version == "" {
t.Error("version is empty, this is unexpected?!")
}
if strings.HasSuffix(version, "\x00") {
t.Error("version ends with \\x00 byte(s)")
}
}
func TestMountOptionsAdd(t *testing.T) { func TestMountOptionsAdd(t *testing.T) {
t.Parallel() t.Parallel()
moaTests := []struct { moaTests := []struct {
@ -241,114 +226,6 @@ func TestMountOptionsAdd(t *testing.T) {
} }
} }
func TestParseKernelRelease(t *testing.T) {
t.Parallel()
badReleases := []string{"x", "5", "5.", "5.4.", "5.x-2-oops", "4.1.x-7-oh", "5.12.x"}
for _, release := range badReleases {
_, _, _, _, err := parseKernelRelease(release)
if err == nil {
t.Errorf("release %q must not be parsed successfully", release)
}
}
goodReleases := []string{
"5.12", "5.12xlinux", "5.1-2-yam", "3.1-5-x", "5.12.14", "5.12.14xlinux",
"5.12.14-xlinux", "5.12.14-99-x", "3.3x-3",
}
goodVersions := [][]int{
{5, 12, 0, 0},
{5, 12, 0, 0},
{5, 1, 0, 2},
{3, 1, 0, 5},
{5, 12, 14, 0},
{5, 12, 14, 0},
{5, 12, 14, 0},
{5, 12, 14, 99},
{3, 3, 0, 0},
}
for i, release := range goodReleases {
version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release)
if err != nil {
t.Errorf("parsing error for release %q: %s", release, err)
}
good := goodVersions[i]
if version != good[0] || patchlevel != good[1] || sublevel != good[2] || extraversion != good[3] {
t.Errorf("release %q parsed incorrectly: expected (%d.%d.%d-%d), actual (%d.%d.%d-%d)",
release, good[0], good[1], good[2], good[3],
version, patchlevel, sublevel, extraversion)
}
}
}
func TestCheckKernelSupport(t *testing.T) {
t.Parallel()
supportsQuota := []string{
"4.17.0",
"5.0.0",
"4.17.0-rc1",
"4.18.0-80.el8",
"3.10.0-1062.el7.x86_64", // 1st backport
"3.10.0-1062.4.1.el7.x86_64", // updated backport
}
noQuota := []string{
"2.6.32-754.15.3.el6.x86_64", // too old
"3.10.0-123.el7.x86_64", // too old for backport
"3.10.0-1062.4.1.el8.x86_64", // nonexisting RHEL-8 kernel
"3.11.0-123.el7.x86_64", // nonexisting RHEL-7 kernel
}
quotaSupport := []KernelVersion{
{4, 17, 0, 0, "", false}, // standard 4.17+ versions
{3, 10, 0, 1062, ".el7", true}, // RHEL-7.7
}
for _, kernel := range supportsQuota {
ok := CheckKernelSupport(kernel, quotaSupport)
if !ok {
t.Errorf("support expected for %s", kernel)
}
}
for _, kernel := range noQuota {
ok := CheckKernelSupport(kernel, quotaSupport)
if ok {
t.Errorf("no support expected for %s", kernel)
}
}
supportsDeepFlatten := []string{
"5.1.0", // 5.1+ supports deep-flatten
"5.3.0",
"4.18.0-193.9.1.el8_2.x86_64", // RHEL 8.2 kernel
}
noDeepFlatten := []string{
"4.18.0", // too old
"3.10.0-123.el7.x86_64", // too old for backport
"3.10.0-1062.4.1.el8.x86_64", // nonexisting RHEL-8 kernel
"3.11.0-123.el7.x86_64", // nonexisting RHEL-7 kernel
}
deepFlattenSupport := []KernelVersion{
{5, 1, 0, 0, "", false}, // standard 5.1+ versions
{4, 18, 0, 193, ".el8", true}, // RHEL 8.2 backport
}
for _, kernel := range supportsDeepFlatten {
ok := CheckKernelSupport(kernel, deepFlattenSupport)
if !ok {
t.Errorf("support expected for %s", kernel)
}
}
for _, kernel := range noDeepFlatten {
ok := CheckKernelSupport(kernel, deepFlattenSupport)
if ok {
t.Errorf("no support expected for %s", kernel)
}
}
}
func TestRoundOffCephFSVolSize(t *testing.T) { func TestRoundOffCephFSVolSize(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {

132
pkg/util/kernel/version.go Normal file
View File

@ -0,0 +1,132 @@
/*
Copyright 2025 The Ceph-CSI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernel
import (
"fmt"
"strings"
"golang.org/x/sys/unix"
"github.com/ceph/ceph-csi/internal/util/log"
)
// GetKernelVersion returns the version of the running Unix (like) system from the
// 'utsname' structs 'release' component.
func GetKernelVersion() (string, error) {
utsname := unix.Utsname{}
if err := unix.Uname(&utsname); err != nil {
return "", err
}
return strings.TrimRight(string(utsname.Release[:]), "\x00"), nil
}
// KernelVersion holds kernel related information.
type KernelVersion struct {
Version int
PatchLevel int
SubLevel int
ExtraVersion int // prefix of the part after the first "-"
Distribution string // component of full extraversion
Backport bool // backport have a fixed version/patchlevel/sublevel
}
// parseKernelRelease parses a kernel release version string into:
// version, patch version, sub version and extra version.
func parseKernelRelease(release string) (int, int, int, int, error) {
version := 0
patchlevel := 0
minVersions := 2
extra := ""
n, err := fmt.Sscanf(release, "%d.%d%s", &version, &patchlevel, &extra)
if n < minVersions && err != nil {
return 0, 0, 0, 0, fmt.Errorf("failed to parse version and patchlevel from %s: %w", release, err)
}
sublevel := 0
extraversion := 0
if n > minVersions {
n, err = fmt.Sscanf(extra, ".%d%s", &sublevel, &extra)
if err != nil && n == 0 && extra != "" && extra[0] != '-' && extra[0] == '.' {
return 0, 0, 0, 0, fmt.Errorf("failed to parse subversion from %s: %w", release, err)
}
extra = strings.TrimPrefix(extra, "-")
// ignore errors, 1st component of extraversion does not need to be an int
_, err = fmt.Sscanf(extra, "%d", &extraversion)
if err != nil {
// "go lint" wants err to be checked...
extraversion = 0
}
}
return version, patchlevel, sublevel, extraversion, nil
}
// CheckKernelSupport checks the running kernel and comparing it to known
// versions that have support for required features . Distributors of
// enterprise Linux have backport quota support to previous versions. This
// function checks if the running kernel is one of the versions that have the
// feature/fixes backport.
//
// `uname -r` (or Uname().Utsname.Release has a format like 1.2.3-rc.vendor
// This can be slit up in the following components: - version (1) - patchlevel
// (2) - sublevel (3) - optional, defaults to 0 - extraversion (rc) - optional,
// matching integers only - distribution (.vendor) - optional, match against
// whole `uname -r` string
//
// For matching multiple versions, the kernelSupport type contains a backport
// bool, which will cause matching
// version+patchlevel+sublevel+(>=extraversion)+(~distribution)
//
// In case the backport bool is false, a simple check for higher versions than
// version+patchlevel+sublevel is done.
func CheckKernelSupport(release string, supportedVersions []KernelVersion) bool {
version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release)
if err != nil {
log.ErrorLogMsg("%v", err)
return false
}
// compare running kernel against known versions
for _, kernel := range supportedVersions {
if !kernel.Backport {
// deal with the default case(s), find >= match for version, patchlevel, sublevel
if version > kernel.Version || (version == kernel.Version && patchlevel > kernel.PatchLevel) ||
(version == kernel.Version && patchlevel == kernel.PatchLevel && sublevel >= kernel.SubLevel) {
return true
}
} else {
// specific backport, match distribution initially
if !strings.Contains(release, kernel.Distribution) {
continue
}
// strict match version, patchlevel, sublevel, and >= match extraversion
if version == kernel.Version && patchlevel == kernel.PatchLevel &&
sublevel == kernel.SubLevel && extraversion >= kernel.ExtraVersion {
return true
}
}
}
log.WarningLogMsg("kernel %s does not support required features", release)
return false
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2025 The Ceph-CSI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kernel
import (
"strings"
"testing"
)
func TestGetKernelVersion(t *testing.T) {
t.Parallel()
version, err := GetKernelVersion()
if err != nil {
t.Errorf("failed to get kernel version: %s", err)
}
if version == "" {
t.Error("version is empty, this is unexpected?!")
}
if strings.HasSuffix(version, "\x00") {
t.Error("version ends with \\x00 byte(s)")
}
}
func TestParseKernelRelease(t *testing.T) {
t.Parallel()
badReleases := []string{"x", "5", "5.", "5.4.", "5.x-2-oops", "4.1.x-7-oh", "5.12.x"}
for _, release := range badReleases {
_, _, _, _, err := parseKernelRelease(release)
if err == nil {
t.Errorf("release %q must not be parsed successfully", release)
}
}
goodReleases := []string{
"5.12", "5.12xlinux", "5.1-2-yam", "3.1-5-x", "5.12.14", "5.12.14xlinux",
"5.12.14-xlinux", "5.12.14-99-x", "3.3x-3",
}
goodVersions := [][]int{
{5, 12, 0, 0},
{5, 12, 0, 0},
{5, 1, 0, 2},
{3, 1, 0, 5},
{5, 12, 14, 0},
{5, 12, 14, 0},
{5, 12, 14, 0},
{5, 12, 14, 99},
{3, 3, 0, 0},
}
for i, release := range goodReleases {
version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release)
if err != nil {
t.Errorf("parsing error for release %q: %s", release, err)
}
good := goodVersions[i]
if version != good[0] || patchlevel != good[1] || sublevel != good[2] || extraversion != good[3] {
t.Errorf("release %q parsed incorrectly: expected (%d.%d.%d-%d), actual (%d.%d.%d-%d)",
release, good[0], good[1], good[2], good[3],
version, patchlevel, sublevel, extraversion)
}
}
}
func TestCheckKernelSupport(t *testing.T) {
t.Parallel()
supportsQuota := []string{
"4.17.0",
"5.0.0",
"4.17.0-rc1",
"4.18.0-80.el8",
"3.10.0-1062.el7.x86_64", // 1st backport
"3.10.0-1062.4.1.el7.x86_64", // updated backport
}
noQuota := []string{
"2.6.32-754.15.3.el6.x86_64", // too old
"3.10.0-123.el7.x86_64", // too old for backport
"3.10.0-1062.4.1.el8.x86_64", // nonexisting RHEL-8 kernel
"3.11.0-123.el7.x86_64", // nonexisting RHEL-7 kernel
}
quotaSupport := []KernelVersion{
{4, 17, 0, 0, "", false}, // standard 4.17+ versions
{3, 10, 0, 1062, ".el7", true}, // RHEL-7.7
}
for _, kernel := range supportsQuota {
ok := CheckKernelSupport(kernel, quotaSupport)
if !ok {
t.Errorf("support expected for %s", kernel)
}
}
for _, kernel := range noQuota {
ok := CheckKernelSupport(kernel, quotaSupport)
if ok {
t.Errorf("no support expected for %s", kernel)
}
}
supportsDeepFlatten := []string{
"5.1.0", // 5.1+ supports deep-flatten
"5.3.0",
"4.18.0-193.9.1.el8_2.x86_64", // RHEL 8.2 kernel
}
noDeepFlatten := []string{
"4.18.0", // too old
"3.10.0-123.el7.x86_64", // too old for backport
"3.10.0-1062.4.1.el8.x86_64", // nonexisting RHEL-8 kernel
"3.11.0-123.el7.x86_64", // nonexisting RHEL-7 kernel
}
deepFlattenSupport := []KernelVersion{
{5, 1, 0, 0, "", false}, // standard 5.1+ versions
{4, 18, 0, 193, ".el8", true}, // RHEL 8.2 backport
}
for _, kernel := range supportsDeepFlatten {
ok := CheckKernelSupport(kernel, deepFlattenSupport)
if !ok {
t.Errorf("support expected for %s", kernel)
}
}
for _, kernel := range noDeepFlatten {
ok := CheckKernelSupport(kernel, deepFlattenSupport)
if ok {
t.Errorf("no support expected for %s", kernel)
}
}
}