diff --git a/internal/cephfs/volumemounter.go b/internal/cephfs/volumemounter.go index 5f1c7f42a..d1c73a5a1 100644 --- a/internal/cephfs/volumemounter.go +++ b/internal/cephfs/volumemounter.go @@ -46,97 +46,26 @@ var ( fusePidMapMtx sync.Mutex fusePidRx = regexp.MustCompile(`(?m)^ceph-fuse\[(.+)\]: starting fuse$`) + + quotaSupport = []util.KernelVersion{ + { + Version: 4, + PatchLevel: 17, + SubLevel: 0, + ExtraVersion: 0, Distribution: "", + Backport: false, + }, // standard 4.17+ versions + { + Version: 3, + PatchLevel: 10, + SubLevel: 0, + ExtraVersion: 1062, + Distribution: ".el7", + Backport: true, + }, // RHEL-7.7 + } ) -// Version checking the running kernel and comparing it to known versions that -// have support for quota. Distributors of enterprise Linux have backported -// quota support to previous versions. This function checks if the running -// kernel is one of the versions that have the feature/fixes backported. -// -// `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 kernelSupportsQuota(release string) bool { - type kernelSupport struct { - version int - patchlevel int - sublevel int - extraversion int // prefix of the part after the first "-" - distribution string // component of full extraversion - backport bool // backports have a fixed version/patchlevel/sublevel - } - - quotaSupport := []kernelSupport{ - {4, 17, 0, 0, "", false}, // standard 4.17+ versions - {3, 10, 0, 1062, ".el7", true}, // RHEL-7.7 - } - - vers := strings.Split(strings.SplitN(release, "-", 2)[0], ".") - version, err := strconv.Atoi(vers[0]) - if err != nil { - klog.Errorf("failed to parse version from %s: %v", release, err) - return false - } - patchlevel, err := strconv.Atoi(vers[1]) - if err != nil { - klog.Errorf("failed to parse patchlevel from %s: %v", release, err) - return false - } - sublevel := 0 - if len(vers) >= 3 { - sublevel, err = strconv.Atoi(vers[2]) - if err != nil { - klog.Errorf("failed to parse sublevel from %s: %v", release, err) - return false - } - } - extra := strings.SplitN(release, "-", 2) - extraversion := 0 - if len(extra) == 2 { - // ignore errors, 1st component of extraversion does not need to be an int - extraversion, err = strconv.Atoi(strings.Split(extra[1], ".")[0]) - if err != nil { - // "go lint" wants err to be checked... - extraversion = 0 - } - } - - // compare running kernel against known versions - for _, kernel := range quotaSupport { - 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 - } - } - } - klog.Errorf("kernel %s does not support quota", release) - return false -} - // Load available ceph mounters installed on system into availableMounters // Called from driver.go's Run() func loadAvailableMounters(conf *util.Config) error { @@ -155,7 +84,7 @@ func loadAvailableMounters(conf *util.Config) error { return kvErr } - if conf.ForceKernelCephFS || kernelSupportsQuota(release) { + if conf.ForceKernelCephFS || util.CheckKernelSupport(release, quotaSupport) { klog.V(1).Infof("loaded mounter: %s", volumeMounterKernel) availableMounters = append(availableMounters, volumeMounterKernel) } else { diff --git a/internal/cephfs/volumemounter_test.go b/internal/cephfs/volumemounter_test.go deleted file mode 100644 index 231dd1b2b..000000000 --- a/internal/cephfs/volumemounter_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2019 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 cephfs - -import ( - "testing" -) - -func init() { -} - -func TestKernelSupportsQuota(t *testing.T) { - 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 - } - - for _, kernel := range supportsQuota { - ok := kernelSupportsQuota(kernel) - if !ok { - t.Errorf("support expected for %s", kernel) - } - } - - for _, kernel := range noQuota { - ok := kernelSupportsQuota(kernel) - if ok { - t.Errorf("no support expected for %s", kernel) - } - } -} diff --git a/internal/util/util.go b/internal/util/util.go index a4c6f05a1..6625ad127 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -21,6 +21,7 @@ import ( "math" "os" "path" + "strconv" "strings" "time" @@ -151,6 +152,90 @@ func KernelVersion() (string, error) { return strings.TrimRight(string(utsname.Release[:]), "\x00"), nil } +// KernelVersion holds kernel related informations +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 // backports have a fixed version/patchlevel/sublevel +} + +// CheckKernelSupport checks the running kernel and comparing it to known +// versions that have support for required features . Distributors of +// enterprise Linux have backported quota support to previous versions. This +// function checks if the running kernel is one of the versions that have the +// feature/fixes backported. +// +// `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 { + vers := strings.Split(strings.SplitN(release, "-", 2)[0], ".") + version, err := strconv.Atoi(vers[0]) + if err != nil { + klog.Errorf("failed to parse version from %s: %v", release, err) + return false + } + patchlevel, err := strconv.Atoi(vers[1]) + if err != nil { + klog.Errorf("failed to parse patchlevel from %s: %v", release, err) + return false + } + sublevel := 0 + if len(vers) >= 3 { + sublevel, err = strconv.Atoi(vers[2]) + if err != nil { + klog.Errorf("failed to parse sublevel from %s: %v", release, err) + return false + } + } + extra := strings.SplitN(release, "-", 2) + extraversion := 0 + if len(extra) == 2 { + // ignore errors, 1st component of extraversion does not need to be an int + extraversion, err = strconv.Atoi(strings.Split(extra[1], ".")[0]) + if err != nil { + // "go lint" wants err to be checked... + extraversion = 0 + } + } + + // 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 + } + } + } + klog.Errorf("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 // to the CO system func GenerateVolID(ctx context.Context, monitors string, cr *Credentials, locationID int64, pool, clusterID, objUUID string, volIDVersion uint16) (string, error) { diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 4aec93db7..9542823ad 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -236,3 +236,67 @@ func TestMountOptionsAdd(t *testing.T) { }) } } +func TestCheckKernelSupport(t *testing.T) { + 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.2.0", + "5.3.0", + } + + 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, 2, 0, 0, "", false}, // standard 5.2+ versions + } + 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) + } + } +}