diff --git a/Gopkg.lock b/Gopkg.lock index 93b490986..1ac3753f1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1366,6 +1366,7 @@ "github.com/pkg/errors", "github.com/prometheus/client_golang/prometheus", "github.com/prometheus/client_golang/prometheus/promhttp", + "golang.org/x/sys/unix", "google.golang.org/grpc", "google.golang.org/grpc/codes", "google.golang.org/grpc/status", diff --git a/pkg/cephfs/volumemounter.go b/pkg/cephfs/volumemounter.go index d48fe4558..ff007013a 100644 --- a/pkg/cephfs/volumemounter.go +++ b/pkg/cephfs/volumemounter.go @@ -29,6 +29,7 @@ import ( "github.com/ceph/ceph-csi/pkg/util" + "golang.org/x/sys/unix" "k8s.io/klog" ) @@ -47,6 +48,95 @@ var ( fusePidRx = regexp.MustCompile(`(?m)^ceph-fuse\[(.+)\]: starting fuse$`) ) +// 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 { @@ -57,21 +147,15 @@ func loadAvailableMounters(conf *util.Config) error { err := kernelMounterProbe.Run() if err == nil { - kernelVersion, _, err := execCommand(context.TODO(), "uname", "-r") - if err != nil { - return err - } - vers := strings.Split(string(kernelVersion), ".") - majorVers, err := strconv.Atoi(vers[0]) - if err != nil { - return err - } - minorVers, err := strconv.Atoi(vers[1]) + // fetch the current running kernel info + utsname := unix.Utsname{} + err := unix.Uname(&utsname) if err != nil { return err } + release := string(utsname.Release[:64]) - if conf.ForceKernelCephFS || majorVers > 4 || (majorVers >= 4 && minorVers >= 17) { + if conf.ForceKernelCephFS || kernelSupportsQuota(release) { klog.Infof("loaded mounter: %s", volumeMounterKernel) availableMounters = append(availableMounters, volumeMounterKernel) } else { diff --git a/pkg/cephfs/volumemounter_test.go b/pkg/cephfs/volumemounter_test.go new file mode 100644 index 000000000..231dd1b2b --- /dev/null +++ b/pkg/cephfs/volumemounter_test.go @@ -0,0 +1,56 @@ +/* +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) + } + } +}