123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- /*
- 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
- }
- // Name is part of the system.Validator interface.
- 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]"
- // kernelConfigPrefix is the prefix of kernel configuration.
- kernelConfigPrefix = "CONFIG_"
- )
- // Validate is part of the system.Validator interface.
- 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 = kernelConfigPrefix + 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
- }
|