mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			265 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			265 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2016 The Kubernetes 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 system
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"compress/gzip"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
 | 
						|
	errorsutil "k8s.io/apimachinery/pkg/util/errors"
 | 
						|
	"k8s.io/klog"
 | 
						|
)
 | 
						|
 | 
						|
var _ Validator = &KernelValidator{}
 | 
						|
 | 
						|
// KernelValidator validates kernel. Currently only validate kernel version
 | 
						|
// and kernel configuration.
 | 
						|
type KernelValidator struct {
 | 
						|
	kernelRelease string
 | 
						|
	Reporter      Reporter
 | 
						|
}
 | 
						|
 | 
						|
func (k *KernelValidator) Name() string {
 | 
						|
	return "kernel"
 | 
						|
}
 | 
						|
 | 
						|
// kConfigOption is the possible kernel config option.
 | 
						|
type kConfigOption string
 | 
						|
 | 
						|
const (
 | 
						|
	builtIn  kConfigOption = "y"
 | 
						|
	asModule kConfigOption = "m"
 | 
						|
	leftOut  kConfigOption = "n"
 | 
						|
 | 
						|
	// validKConfigRegex is the regex matching kernel configuration line.
 | 
						|
	validKConfigRegex = "^CONFIG_[A-Z0-9_]+=[myn]"
 | 
						|
	// kConfigPrefix is the prefix of kernel configuration.
 | 
						|
	kConfigPrefix = "CONFIG_"
 | 
						|
)
 | 
						|
 | 
						|
func (k *KernelValidator) Validate(spec SysSpec) (error, error) {
 | 
						|
	helper := KernelValidatorHelperImpl{}
 | 
						|
	release, err := helper.GetKernelReleaseVersion()
 | 
						|
	if err != nil {
 | 
						|
		return nil, errors.Wrap(err, "failed to get kernel release")
 | 
						|
	}
 | 
						|
	k.kernelRelease = release
 | 
						|
	var errs []error
 | 
						|
	errs = append(errs, k.validateKernelVersion(spec.KernelSpec))
 | 
						|
	// only validate kernel config when necessary (currently no kernel config for windows)
 | 
						|
	if len(spec.KernelSpec.Required) > 0 || len(spec.KernelSpec.Forbidden) > 0 || len(spec.KernelSpec.Optional) > 0 {
 | 
						|
		errs = append(errs, k.validateKernelConfig(spec.KernelSpec))
 | 
						|
	}
 | 
						|
	return nil, errorsutil.NewAggregate(errs)
 | 
						|
}
 | 
						|
 | 
						|
// validateKernelVersion validates the kernel version.
 | 
						|
func (k *KernelValidator) validateKernelVersion(kSpec KernelSpec) error {
 | 
						|
	versionRegexps := kSpec.Versions
 | 
						|
	for _, versionRegexp := range versionRegexps {
 | 
						|
		r := regexp.MustCompile(versionRegexp)
 | 
						|
		if r.MatchString(k.kernelRelease) {
 | 
						|
			k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, good)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, bad)
 | 
						|
	return errors.Errorf("unsupported kernel release: %s", k.kernelRelease)
 | 
						|
}
 | 
						|
 | 
						|
// validateKernelConfig validates the kernel configurations.
 | 
						|
func (k *KernelValidator) validateKernelConfig(kSpec KernelSpec) error {
 | 
						|
	allConfig, err := k.getKernelConfig()
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "failed to parse kernel config")
 | 
						|
	}
 | 
						|
	return k.validateCachedKernelConfig(allConfig, kSpec)
 | 
						|
}
 | 
						|
 | 
						|
// validateCachedKernelConfig validates the kernel confgiurations cached in internal data type.
 | 
						|
func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfigOption, kSpec KernelSpec) error {
 | 
						|
	badConfigs := []string{}
 | 
						|
	// reportAndRecord is a helper function to record bad config when
 | 
						|
	// report.
 | 
						|
	reportAndRecord := func(name, msg, desc string, result ValidationResultType) {
 | 
						|
		if result == bad {
 | 
						|
			badConfigs = append(badConfigs, name)
 | 
						|
		}
 | 
						|
		// report description when the config is bad or warn.
 | 
						|
		if result != good && desc != "" {
 | 
						|
			msg = msg + " - " + desc
 | 
						|
		}
 | 
						|
		k.Reporter.Report(name, msg, result)
 | 
						|
	}
 | 
						|
	const (
 | 
						|
		required = iota
 | 
						|
		optional
 | 
						|
		forbidden
 | 
						|
	)
 | 
						|
	validateOpt := func(config KernelConfig, expect int) {
 | 
						|
		var found, missing ValidationResultType
 | 
						|
		switch expect {
 | 
						|
		case required:
 | 
						|
			found, missing = good, bad
 | 
						|
		case optional:
 | 
						|
			found, missing = good, warn
 | 
						|
		case forbidden:
 | 
						|
			found, missing = bad, good
 | 
						|
		}
 | 
						|
		var name string
 | 
						|
		var opt kConfigOption
 | 
						|
		var ok bool
 | 
						|
		for _, name = range append([]string{config.Name}, config.Aliases...) {
 | 
						|
			name = kConfigPrefix + name
 | 
						|
			if opt, ok = allConfig[name]; ok {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !ok {
 | 
						|
			reportAndRecord(name, "not set", config.Description, missing)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		switch opt {
 | 
						|
		case builtIn:
 | 
						|
			reportAndRecord(name, "enabled", config.Description, found)
 | 
						|
		case asModule:
 | 
						|
			reportAndRecord(name, "enabled (as module)", config.Description, found)
 | 
						|
		case leftOut:
 | 
						|
			reportAndRecord(name, "disabled", config.Description, missing)
 | 
						|
		default:
 | 
						|
			reportAndRecord(name, fmt.Sprintf("unknown option: %s", opt), config.Description, missing)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, config := range kSpec.Required {
 | 
						|
		validateOpt(config, required)
 | 
						|
	}
 | 
						|
	for _, config := range kSpec.Optional {
 | 
						|
		validateOpt(config, optional)
 | 
						|
	}
 | 
						|
	for _, config := range kSpec.Forbidden {
 | 
						|
		validateOpt(config, forbidden)
 | 
						|
	}
 | 
						|
	if len(badConfigs) > 0 {
 | 
						|
		return errors.Errorf("unexpected kernel config: %s", strings.Join(badConfigs, " "))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// getKernelConfigReader search kernel config file in a predefined list. Once the kernel config
 | 
						|
// file is found it will read the configurations into a byte buffer and return. If the kernel
 | 
						|
// config file is not found, it will try to load kernel config module and retry again.
 | 
						|
func (k *KernelValidator) getKernelConfigReader() (io.Reader, error) {
 | 
						|
	possibePaths := []string{
 | 
						|
		"/proc/config.gz",
 | 
						|
		"/boot/config-" + k.kernelRelease,
 | 
						|
		"/usr/src/linux-" + k.kernelRelease + "/.config",
 | 
						|
		"/usr/src/linux/.config",
 | 
						|
		"/usr/lib/modules/" + k.kernelRelease + "/config",
 | 
						|
		"/usr/lib/ostree-boot/config-" + k.kernelRelease,
 | 
						|
		"/usr/lib/kernel/config-" + k.kernelRelease,
 | 
						|
		"/usr/src/linux-headers-" + k.kernelRelease + "/.config",
 | 
						|
		"/lib/modules/" + k.kernelRelease + "/build/.config",
 | 
						|
	}
 | 
						|
	configsModule := "configs"
 | 
						|
	modprobeCmd := "modprobe"
 | 
						|
	// loadModule indicates whether we've tried to load kernel config module ourselves.
 | 
						|
	loadModule := false
 | 
						|
	for {
 | 
						|
		for _, path := range possibePaths {
 | 
						|
			_, err := os.Stat(path)
 | 
						|
			if err != nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			// Buffer the whole file, so that we can close the file and unload
 | 
						|
			// kernel config module in this function.
 | 
						|
			b, err := ioutil.ReadFile(path)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			var r io.Reader
 | 
						|
			r = bytes.NewReader(b)
 | 
						|
			// This is a gzip file (config.gz), unzip it.
 | 
						|
			if filepath.Ext(path) == ".gz" {
 | 
						|
				r, err = gzip.NewReader(r)
 | 
						|
				if err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return r, nil
 | 
						|
		}
 | 
						|
		// If we've tried to load kernel config module, break and return error.
 | 
						|
		if loadModule {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		// If the kernel config file is not found, try to load the kernel
 | 
						|
		// config module and check again.
 | 
						|
		output, err := exec.Command(modprobeCmd, configsModule).CombinedOutput()
 | 
						|
		if err != nil {
 | 
						|
			return nil, errors.Wrapf(err, "unable to load kernel module: %q, output: %q, err",
 | 
						|
				configsModule, output)
 | 
						|
		}
 | 
						|
		// Unload the kernel config module to make sure the validation have no side effect.
 | 
						|
		defer exec.Command(modprobeCmd, "-r", configsModule).Run()
 | 
						|
		loadModule = true
 | 
						|
	}
 | 
						|
	return nil, errors.Errorf("no config path in %v is available", possibePaths)
 | 
						|
}
 | 
						|
 | 
						|
// getKernelConfig gets kernel config from kernel config file and convert kernel config to internal type.
 | 
						|
func (k *KernelValidator) getKernelConfig() (map[string]kConfigOption, error) {
 | 
						|
	r, err := k.getKernelConfigReader()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return k.parseKernelConfig(r)
 | 
						|
}
 | 
						|
 | 
						|
// parseKernelConfig converts kernel config to internal type.
 | 
						|
func (k *KernelValidator) parseKernelConfig(r io.Reader) (map[string]kConfigOption, error) {
 | 
						|
	config := map[string]kConfigOption{}
 | 
						|
	regex := regexp.MustCompile(validKConfigRegex)
 | 
						|
	s := bufio.NewScanner(r)
 | 
						|
	for s.Scan() {
 | 
						|
		if err := s.Err(); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		line := strings.TrimSpace(s.Text())
 | 
						|
		if !regex.MatchString(line) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		fields := strings.Split(line, "=")
 | 
						|
		if len(fields) != 2 {
 | 
						|
			klog.Errorf("Unexpected fields number in config %q", line)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		config[fields[0]] = kConfigOption(fields[1])
 | 
						|
	}
 | 
						|
	return config, nil
 | 
						|
 | 
						|
}
 |