diff --git a/container/docker/factory.go b/container/docker/factory.go index 68d5046816..1f4915bd94 100644 --- a/container/docker/factory.go +++ b/container/docker/factory.go @@ -19,15 +19,18 @@ import ( "fmt" "path" "regexp" + "strconv" "strings" "sync" + "github.com/blang/semver" dockertypes "github.com/docker/engine-api/types" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" "github.com/google/cadvisor/devicemapper" "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/machine" "github.com/google/cadvisor/manager/watcher" dockerutil "github.com/google/cadvisor/utils/docker" @@ -178,6 +181,10 @@ func startThinPoolWatcher(dockerInfo *dockertypes.Info) (*devicemapper.ThinPoolW return nil, err } + if err := ensureThinLsKernelVersion(machine.KernelVersion()); err != nil { + return nil, err + } + dockerThinPoolName, err := dockerutil.DockerThinPoolName(*dockerInfo) if err != nil { return nil, err @@ -197,6 +204,66 @@ func startThinPoolWatcher(dockerInfo *dockertypes.Info) (*devicemapper.ThinPoolW return thinPoolWatcher, nil } +func ensureThinLsKernelVersion(kernelVersion string) error { + // kernel 4.4.0 has the proper bug fixes to allow thin_ls to work without corrupting the thin pool + minKernelVersion := semver.MustParse("4.4.0") + // RHEL 7 kernel 3.10.0 release >= 366 has the proper bug fixes backported from 4.4.0 to allow + // thin_ls to work without corrupting the thin pool + minRhel7KernelVersion := semver.MustParse("3.10.0") + + matches := version_re.FindStringSubmatch(kernelVersion) + if len(matches) < 4 { + return fmt.Errorf("error parsing kernel version: %q is not a semver", kernelVersion) + } + + sem, err := semver.Make(matches[0]) + if err != nil { + return err + } + + if sem.GTE(minKernelVersion) { + // kernel 4.4+ - good + return nil + } + + // Certain RHEL/Centos 7.x kernels have a backport to fix the corruption bug + if !strings.Contains(kernelVersion, ".el7") { + // not a RHEL 7.x kernel - won't work + return fmt.Errorf("kernel version 4.4.0 or later is required to use thin_ls - you have %q", kernelVersion) + } + + // RHEL/Centos 7.x from here on + if sem.Major != 3 { + // only 3.x kernels *may* work correctly + return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion) + } + + if sem.GT(minRhel7KernelVersion) { + // 3.10.1+ - good + return nil + } + + if sem.EQ(minRhel7KernelVersion) { + // need to check release + releaseRE := regexp.MustCompile(`^[^-]+-([0-9]+)\.`) + releaseMatches := releaseRE.FindStringSubmatch(kernelVersion) + if len(releaseMatches) != 2 { + return fmt.Errorf("unable to determine RHEL/Centos 7.x kernel release from %q", kernelVersion) + } + + release, err := strconv.Atoi(releaseMatches[1]) + if err != nil { + return fmt.Errorf("error parsing release %q: %v", releaseMatches[1], err) + } + + if release >= 366 { + return nil + } + } + + return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion) +} + // Register root container before running this function! func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, ignoreMetrics container.MetricSet) error { client, err := Client() diff --git a/container/docker/factory_test.go b/container/docker/factory_test.go new file mode 100644 index 0000000000..3131aac858 --- /dev/null +++ b/container/docker/factory_test.go @@ -0,0 +1,51 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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 docker + +import "testing" + +func TestEnsureThinLsKernelVersion(t *testing.T) { + tests := []struct { + version string + expectedError string + }{ + {"4.4.0-31-generic", ""}, + {"4.4.1", ""}, + {"4.6.4-301.fc24.x86_64", ""}, + {"3.10.0-327.22.2.el7.x86_64", `RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have "3.10.0-327.22.2.el7.x86_64"`}, + {"3.10.0-366.el7.x86_64", ""}, + {"3.10.0-366.el7_3.x86_64", ""}, + {"3.10.0.el7.abc", `unable to determine RHEL/Centos 7.x kernel release from "3.10.0.el7.abc"`}, + {"3.10.0-abc.el7.blarg", `unable to determine RHEL/Centos 7.x kernel release from "3.10.0-abc.el7.blarg"`}, + {"3.10.0-367.el7.x86_64", ""}, + {"3.10.0-366.x86_64", `kernel version 4.4.0 or later is required to use thin_ls - you have "3.10.0-366.x86_64"`}, + {"3.10.1-1.el7.x86_64", ""}, + {"2.0.36", `kernel version 4.4.0 or later is required to use thin_ls - you have "2.0.36"`}, + {"2.1", `error parsing kernel version: "2.1" is not a semver`}, + } + + for _, test := range tests { + err := ensureThinLsKernelVersion(test.version) + if err != nil { + if len(test.expectedError) == 0 { + t.Errorf("%s: expected no error, got %v", test.version, err) + } else if err.Error() != test.expectedError { + t.Errorf("%s: expected error %v, got %v", test.version, test.expectedError, err) + } + } else if err == nil && len(test.expectedError) > 0 { + t.Errorf("%s: expected error %v", test.version, test.expectedError) + } + } +}