github.com/google/cadvisor@v0.49.1/validate/validate.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Handler for /validate content.
    16  // Validates cadvisor dependencies - kernel, os, docker setup.
    17  
    18  package validate
    19  
    20  import (
    21  	"fmt"
    22  	"log"
    23  	"net/http"
    24  	"os"
    25  	"path"
    26  	"strings"
    27  
    28  	"github.com/google/cadvisor/container/docker"
    29  	"github.com/google/cadvisor/manager"
    30  	"github.com/google/cadvisor/utils"
    31  
    32  	"github.com/opencontainers/runc/libcontainer/cgroups"
    33  )
    34  
    35  const (
    36  	ValidatePage  = "/validate/"
    37  	Supported     = "[Supported, but not recommended]"
    38  	Unsupported   = "[Unsupported]"
    39  	Recommended   = "[Supported and recommended]"
    40  	Unknown       = "[Unknown]"
    41  	VersionFormat = "%d.%d%s"
    42  	OutputFormat  = "%s: %s\n\t%s\n\n"
    43  )
    44  
    45  func getMajorMinor(version string) (int, int, error) {
    46  	var major, minor int
    47  	var ign string
    48  	n, err := fmt.Sscanf(version, VersionFormat, &major, &minor, &ign)
    49  	if n != 3 || err != nil {
    50  		log.Printf("Failed to parse version for %s", version)
    51  		return -1, -1, err
    52  	}
    53  	return major, minor, nil
    54  }
    55  
    56  func validateKernelVersion(version string) (string, string) {
    57  	desc := fmt.Sprintf("Kernel version is %s. Versions >= 2.6 are supported. 3.0+ are recommended.\n", version)
    58  	major, minor, err := getMajorMinor(version)
    59  	if err != nil {
    60  		desc = fmt.Sprintf("Could not parse kernel version. %s", desc)
    61  		return Unknown, desc
    62  	}
    63  
    64  	if major < 2 {
    65  		return Unsupported, desc
    66  	}
    67  
    68  	if major == 2 && minor < 6 {
    69  		return Unsupported, desc
    70  	}
    71  
    72  	if major >= 3 {
    73  		return Recommended, desc
    74  	}
    75  
    76  	return Supported, desc
    77  }
    78  
    79  func validateDockerVersion(version string) (string, string) {
    80  	desc := fmt.Sprintf("Docker version is %s. Versions >= 1.0 are supported. 1.2+ are recommended.\n", version)
    81  	major, minor, err := getMajorMinor(version)
    82  	if err != nil {
    83  		desc = fmt.Sprintf("Could not parse docker version. %s\n\t", desc)
    84  		return Unknown, desc
    85  	}
    86  	if major < 1 {
    87  		return Unsupported, desc
    88  	}
    89  
    90  	if major == 1 && minor < 2 {
    91  		return Supported, desc
    92  	}
    93  
    94  	return Recommended, desc
    95  }
    96  
    97  func getEnabledCgroups() (map[string]int, error) {
    98  	out, err := os.ReadFile("/proc/cgroups")
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	cgroups := make(map[string]int)
   103  	for i, line := range strings.Split(string(out), "\n") {
   104  		var cgroup string
   105  		var ign, enabled int
   106  		if i == 0 || line == "" {
   107  			continue
   108  		}
   109  		n, err := fmt.Sscanf(line, "%s %d %d %d", &cgroup, &ign, &ign, &enabled)
   110  		if n != 4 || err != nil {
   111  			if err == nil {
   112  				err = fmt.Errorf("failed to parse /proc/cgroup entry %s", line)
   113  			}
   114  			return nil, err
   115  		}
   116  		cgroups[cgroup] = enabled
   117  	}
   118  	return cgroups, nil
   119  }
   120  
   121  func areCgroupsPresent(available map[string]int, desired []string) (bool, string) {
   122  	for _, cgroup := range desired {
   123  		enabled, ok := available[cgroup]
   124  		if !ok {
   125  			reason := fmt.Sprintf("Missing cgroup %s. Available cgroups: %v\n", cgroup, available)
   126  			return false, reason
   127  		}
   128  		if enabled != 1 {
   129  			reason := fmt.Sprintf("Cgroup %s not enabled. Available cgroups: %v\n", cgroup, available)
   130  			return false, reason
   131  		}
   132  	}
   133  	return true, ""
   134  }
   135  
   136  func validateCPUCFSBandwidth(availableCgroups map[string]int) string {
   137  	ok, _ := areCgroupsPresent(availableCgroups, []string{"cpu"})
   138  	if !ok {
   139  		return "\tCpu cfs bandwidth status unknown: cpu cgroup not enabled.\n"
   140  	}
   141  	mnt, err := cgroups.FindCgroupMountpoint("/", "cpu")
   142  	if err != nil {
   143  		return "\tCpu cfs bandwidth status unknown: cpu cgroup not mounted.\n"
   144  	}
   145  	_, err = os.Stat(path.Join(mnt, "cpu.cfs_period_us"))
   146  	if os.IsNotExist(err) {
   147  		return "\tCpu cfs bandwidth is disabled. Recompile kernel with \"CONFIG_CFS_BANDWIDTH\" enabled.\n"
   148  	}
   149  
   150  	return "\tCpu cfs bandwidth is enabled.\n"
   151  }
   152  
   153  func validateMemoryAccounting(availableCgroups map[string]int) string {
   154  	ok, _ := areCgroupsPresent(availableCgroups, []string{"memory"})
   155  	if !ok {
   156  		return "\tHierarchical memory accounting status unknown: memory cgroup not enabled.\n"
   157  	}
   158  	var enabled int
   159  	if cgroups.IsCgroup2UnifiedMode() {
   160  		enabled = 1
   161  	} else {
   162  		mnt, err := cgroups.FindCgroupMountpoint("/", "memory")
   163  		if err != nil {
   164  			return "\tHierarchical memory accounting status unknown: memory cgroup not mounted.\n"
   165  		}
   166  		hier, err := os.ReadFile(path.Join(mnt, "memory.use_hierarchy"))
   167  		if err != nil {
   168  			return "\tHierarchical memory accounting status unknown: hierarchy interface unavailable.\n"
   169  		}
   170  		n, err := fmt.Sscanf(string(hier), "%d", &enabled)
   171  		if err != nil || n != 1 {
   172  			return "\tHierarchical memory accounting status unknown: hierarchy interface unreadable.\n"
   173  		}
   174  	}
   175  	if enabled == 1 {
   176  		return "\tHierarchical memory accounting enabled. Reported memory usage includes memory used by child containers.\n"
   177  	}
   178  	return "\tHierarchical memory accounting disabled. Memory usage does not include usage from child containers.\n"
   179  
   180  }
   181  
   182  func validateCgroups() (string, string) {
   183  	requiredCgroups := []string{"cpu", "cpuacct"}
   184  	recommendedCgroups := []string{"memory", "blkio", "cpuset", "devices", "freezer"}
   185  	availableCgroups, err := getEnabledCgroups()
   186  	desc := fmt.Sprintf("\tFollowing cgroups are required: %v\n\tFollowing other cgroups are recommended: %v\n", requiredCgroups, recommendedCgroups)
   187  	if err != nil {
   188  		desc = fmt.Sprintf("Could not parse /proc/cgroups.\n%s", desc)
   189  		return Unknown, desc
   190  	}
   191  	ok, out := areCgroupsPresent(availableCgroups, requiredCgroups)
   192  	if !ok {
   193  		out += desc
   194  		return Unsupported, out
   195  	}
   196  	ok, out = areCgroupsPresent(availableCgroups, recommendedCgroups)
   197  	if !ok {
   198  		// supported, but not recommended.
   199  		out += desc
   200  		return Supported, out
   201  	}
   202  	out = fmt.Sprintf("Available cgroups: %v\n", availableCgroups)
   203  	out += desc
   204  	out += validateMemoryAccounting(availableCgroups)
   205  	out += validateCPUCFSBandwidth(availableCgroups)
   206  	return Recommended, out
   207  }
   208  
   209  func validateDockerInfo() (string, string) {
   210  	info, err := docker.ValidateInfo(docker.Info, docker.VersionString)
   211  	if err != nil {
   212  		return Unsupported, fmt.Sprintf("Docker setup is invalid: %v", err)
   213  	}
   214  
   215  	desc := fmt.Sprintf("Storage driver is %s.\n", info.Driver)
   216  	return Recommended, desc
   217  }
   218  
   219  func validateCgroupMounts() (string, string) {
   220  	const recommendedMount = "/sys/fs/cgroup"
   221  	desc := fmt.Sprintf("\tAny cgroup mount point that is detectible and accessible is supported. %s is recommended as a standard location.\n", recommendedMount)
   222  	mnt, err := cgroups.FindCgroupMountpoint("/", "cpu")
   223  	if err != nil {
   224  		out := "Could not locate cgroup mount point.\n"
   225  		out += desc
   226  		return Unknown, out
   227  	}
   228  	mnt = path.Dir(mnt)
   229  	if !utils.FileExists(mnt) {
   230  		out := fmt.Sprintf("Cgroup mount directory %s inaccessible.\n", mnt)
   231  		out += desc
   232  		return Unsupported, out
   233  	}
   234  	mounts, err := os.ReadDir(mnt)
   235  	if err != nil {
   236  		out := fmt.Sprintf("Could not read cgroup mount directory %s.\n", mnt)
   237  		out += desc
   238  		return Unsupported, out
   239  	}
   240  	mountNames := "\tCgroup mount directories: "
   241  	for _, mount := range mounts {
   242  		mountNames += mount.Name() + " "
   243  	}
   244  	mountNames += "\n"
   245  	out := fmt.Sprintf("Cgroups are mounted at %s.\n", mnt)
   246  	out += mountNames
   247  	out += desc
   248  	info, err := os.ReadFile("/proc/mounts")
   249  	if err != nil {
   250  		out := "Could not read /proc/mounts.\n"
   251  		out += desc
   252  		return Unsupported, out
   253  	}
   254  	out += "\tCgroup mounts:\n"
   255  	for _, line := range strings.Split(string(info), "\n") {
   256  		if strings.Contains(line, " cgroup ") {
   257  			out += "\t" + line + "\n"
   258  		}
   259  	}
   260  	if mnt == recommendedMount {
   261  		return Recommended, out
   262  	}
   263  	return Supported, out
   264  }
   265  
   266  func validateIoScheduler(containerManager manager.Manager) (string, string) {
   267  	var desc string
   268  	mi, err := containerManager.GetMachineInfo()
   269  	if err != nil {
   270  		return Unknown, "Machine info not available\n\t"
   271  	}
   272  	cfq := false
   273  	for _, disk := range mi.DiskMap {
   274  		desc += fmt.Sprintf("\t Disk %q Scheduler type %q.\n", disk.Name, disk.Scheduler)
   275  		if disk.Scheduler == "cfq" {
   276  			cfq = true
   277  		}
   278  	}
   279  	// Since we get lot of random block devices, report recommended if
   280  	// at least one of them is on cfq. Report Supported otherwise.
   281  	if cfq {
   282  		desc = "At least one device supports 'cfq' I/O scheduler. Some disk stats can be reported.\n" + desc
   283  		return Recommended, desc
   284  	}
   285  	desc = "None of the devices support 'cfq' I/O scheduler. No disk stats can be reported.\n" + desc
   286  	return Supported, desc
   287  }
   288  
   289  func HandleRequest(w http.ResponseWriter, containerManager manager.Manager) error {
   290  	// Get cAdvisor version Info.
   291  	versionInfo, err := containerManager.GetVersionInfo()
   292  	if err != nil {
   293  		return err
   294  	}
   295  
   296  	out := fmt.Sprintf("cAdvisor version: %s\n\n", versionInfo.CadvisorVersion)
   297  
   298  	// No OS is preferred or unsupported as of now.
   299  	out += fmt.Sprintf("OS version: %s\n\n", versionInfo.ContainerOsVersion)
   300  
   301  	kernelValidation, desc := validateKernelVersion(versionInfo.KernelVersion)
   302  	out += fmt.Sprintf(OutputFormat, "Kernel version", kernelValidation, desc)
   303  
   304  	cgroupValidation, desc := validateCgroups()
   305  	out += fmt.Sprintf(OutputFormat, "Cgroup setup", cgroupValidation, desc)
   306  
   307  	mountsValidation, desc := validateCgroupMounts()
   308  	out += fmt.Sprintf(OutputFormat, "Cgroup mount setup", mountsValidation, desc)
   309  
   310  	dockerValidation, desc := validateDockerVersion(versionInfo.DockerVersion)
   311  	out += fmt.Sprintf(OutputFormat, "Docker version", dockerValidation, desc)
   312  
   313  	dockerInfoValidation, desc := validateDockerInfo()
   314  	out += fmt.Sprintf(OutputFormat, "Docker driver setup", dockerInfoValidation, desc)
   315  
   316  	ioSchedulerValidation, desc := validateIoScheduler(containerManager)
   317  	out += fmt.Sprintf(OutputFormat, "Block device setup", ioSchedulerValidation, desc)
   318  
   319  	// Output debug info.
   320  	debugInfo := containerManager.DebugInfo()
   321  	for category, lines := range debugInfo {
   322  		out += fmt.Sprintf(OutputFormat, category, "", strings.Join(lines, "\n\t"))
   323  	}
   324  
   325  	_, err = w.Write([]byte(out))
   326  	return err
   327  }