diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index b2cd367909a..cbee20a07a4 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -549,6 +549,7 @@ system-cgroups system-container system-pods-startup-timeout system-reserved +system-validate-mode target-port target-ram-mb tcp-services diff --git a/test/e2e_node/e2e_node_suite_test.go b/test/e2e_node/e2e_node_suite_test.go index 73a539893a9..821089e635e 100644 --- a/test/e2e_node/e2e_node_suite_test.go +++ b/test/e2e_node/e2e_node_suite_test.go @@ -36,8 +36,10 @@ import ( commontest "k8s.io/kubernetes/test/e2e/common" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e_node/services" + "k8s.io/kubernetes/test/e2e_node/system" "github.com/golang/glog" + "github.com/kardianos/osext" . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/config" more_reporters "github.com/onsi/ginkgo/reporters" @@ -47,7 +49,9 @@ import ( var e2es *services.E2EServices +// TODO(random-liu): Change the following modes to sub-command. var runServicesMode = flag.Bool("run-services-mode", false, "If true, only run services (etcd, apiserver) in current process, and not run test.") +var systemValidateMode = flag.Bool("system-validate-mode", false, "If true, only run system validation in current process, and not run test.") func init() { framework.RegisterCommonFlags() @@ -73,6 +77,13 @@ func TestE2eNode(t *testing.T) { services.RunE2EServices() return } + if *systemValidateMode { + // If system-validate-mode is specified, only run system validation in current process. + if err := system.Validate(); err != nil { + glog.Exitf("system validation failed: %v", err) + } + return + } // If run-services-mode is not specified, run test. rand.Seed(time.Now().UTC().UnixNano()) RegisterFailHandler(Fail) @@ -100,6 +111,10 @@ var _ = SynchronizedBeforeSuite(func() []byte { Expect(err).NotTo(HaveOccurred(), "should be able to get node name") framework.TestContext.NodeName = hostname } + + // Run system validation test. + Expect(validateSystem()).To(Succeed(), "system validation") + // Pre-pull the images tests depend on so we can fail immediately if there is an image pull issue // This helps with debugging test flakes since it is hard to tell when a test failure is due to image pulling. if framework.TestContext.PrepullImages { @@ -151,6 +166,22 @@ var _ = SynchronizedAfterSuite(func() {}, func() { glog.Infof("Tests Finished") }) +// validateSystem runs system validation in a separate process and returns error if validation fails. +func validateSystem() error { + testBin, err := osext.Executable() + if err != nil { + return fmt.Errorf("can't get current binary: %v", err) + } + // TODO(random-liu): Remove sudo in containerize PR. + output, err := exec.Command("sudo", testBin, "--system-validate-mode").CombinedOutput() + // The output of system validation should have been formatted, directly print here. + fmt.Print(string(output)) + if err != nil { + return fmt.Errorf("system validation failed") + } + return nil +} + func maskLocksmithdOnCoreos() { data, err := ioutil.ReadFile("/etc/os-release") if err != nil { diff --git a/test/e2e_node/system/cgroup_validator.go b/test/e2e_node/system/cgroup_validator.go new file mode 100644 index 00000000000..6ec1d76fcfa --- /dev/null +++ b/test/e2e_node/system/cgroup_validator.go @@ -0,0 +1,93 @@ +/* +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" + "fmt" + "os" + "strings" +) + +var _ Validator = &CgroupsValidator{} + +type CgroupsValidator struct{} + +func (c *CgroupsValidator) Name() string { + return "cgroups" +} + +const ( + cgroupsConfigPrefix = "CGROUPS_" +) + +func (c *CgroupsValidator) Validate(spec SysSpec) error { + subsystems, err := c.getCgroupSubsystems() + if err != nil { + return fmt.Errorf("failed to get cgroup subsystems: %v", err) + } + return c.validateCgroupSubsystems(spec.Cgroups, subsystems) +} + +func (c *CgroupsValidator) validateCgroupSubsystems(cgroupSpec, subsystems []string) error { + missing := []string{} + for _, cgroup := range cgroupSpec { + found := false + for _, subsystem := range subsystems { + if cgroup == subsystem { + found = true + break + } + } + item := cgroupsConfigPrefix + strings.ToUpper(cgroup) + if found { + report(item, "enabled", good) + } else { + report(item, "missing", bad) + missing = append(missing, cgroup) + } + } + if len(missing) > 0 { + return fmt.Errorf("missing cgroups: %s", strings.Join(missing, " ")) + } + return nil + +} + +func (c *CgroupsValidator) getCgroupSubsystems() ([]string, error) { + f, err := os.Open("/proc/cgroups") + if err != nil { + return nil, err + } + defer f.Close() + + subsystems := []string{} + s := bufio.NewScanner(f) + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + text := s.Text() + if text[0] != '#' { + parts := strings.Fields(text) + if len(parts) >= 4 && parts[3] != "0" { + subsystems = append(subsystems, parts[0]) + } + } + } + return subsystems, nil +} diff --git a/test/e2e_node/system/docker_validator.go b/test/e2e_node/system/docker_validator.go new file mode 100644 index 00000000000..0e207df8cb4 --- /dev/null +++ b/test/e2e_node/system/docker_validator.go @@ -0,0 +1,84 @@ +/* +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 ( + "fmt" + "regexp" + + "github.com/docker/engine-api/client" + "github.com/docker/engine-api/types" + "golang.org/x/net/context" +) + +var _ Validator = &DockerValidator{} + +// DockerValidator validates docker configuration. +type DockerValidator struct{} + +func (d *DockerValidator) Name() string { + return "docker" +} + +const ( + dockerEndpoint = "unix:///var/run/docker.sock" + dockerConfigPrefix = "DOCKER_" +) + +// TODO(random-liu): Add more validating items. +func (d *DockerValidator) Validate(spec SysSpec) error { + if spec.RuntimeSpec.DockerSpec == nil { + // If DockerSpec is not specified, assume current runtime is not + // docker, skip the docker configuration validation. + return nil + } + c, err := client.NewClient(dockerEndpoint, "", nil, nil) + if err != nil { + return fmt.Errorf("failed to create docker client: %v", err) + } + info, err := c.Info(context.Background()) + if err != nil { + return fmt.Errorf("failed to get docker info: %v", err) + } + return d.validateDockerInfo(spec.RuntimeSpec.DockerSpec, info) +} + +func (d *DockerValidator) validateDockerInfo(spec *DockerSpec, info types.Info) error { + // Validate docker version. + matched := false + for _, v := range spec.Version { + r := regexp.MustCompile(v) + if r.MatchString(info.ServerVersion) { + report(dockerConfigPrefix+"VERSION", info.ServerVersion, good) + matched = true + } + } + if !matched { + report(dockerConfigPrefix+"VERSION", info.ServerVersion, bad) + return fmt.Errorf("unsupported docker version: %s", info.ServerVersion) + } + // Validate graph driver. + item := dockerConfigPrefix + "GRAPH_DRIVER" + for _, d := range spec.GraphDriver { + if info.Driver == d { + report(item, info.Driver, good) + return nil + } + } + report(item, info.Driver, bad) + return fmt.Errorf("unsupported graph driver: %s", info.Driver) +} diff --git a/test/e2e_node/system/kernel_validator.go b/test/e2e_node/system/kernel_validator.go new file mode 100644 index 00000000000..f71d7072e32 --- /dev/null +++ b/test/e2e_node/system/kernel_validator.go @@ -0,0 +1,255 @@ +/* +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/golang/glog" + "k8s.io/kubernetes/pkg/util/errors" +) + +var _ Validator = &KernelValidator{} + +// KernelValidator validates kernel. Currently only validate kernel version +// and kernel configuration. +type KernelValidator struct { + kernelRelease string +} + +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 { + out, err := exec.Command("uname", "-r").CombinedOutput() + if err != nil { + return fmt.Errorf("failed to get kernel release: %v", err) + } + k.kernelRelease = strings.TrimSpace(string(out)) + var errs []error + errs = append(errs, k.validateKernelVersion(spec.KernelSpec)) + errs = append(errs, k.validateKernelConfig(spec.KernelSpec)) + return errors.NewAggregate(errs) +} + +// validateKernelVersion validates the kernel version. +func (k *KernelValidator) validateKernelVersion(kSpec KernelSpec) error { + glog.Infof("Validating kernel version") + versionRegexps := kSpec.Versions + for _, versionRegexp := range versionRegexps { + r := regexp.MustCompile(versionRegexp) + if r.MatchString(k.kernelRelease) { + report("KERNEL_VERSION", k.kernelRelease, good) + return nil + } + } + report("KERNEL_VERSION", k.kernelRelease, bad) + return fmt.Errorf("unsupported kernel release: %s", k.kernelRelease) +} + +// validateKernelConfig validates the kernel configurations. +func (k *KernelValidator) validateKernelConfig(kSpec KernelSpec) error { + glog.Infof("Validating kernel config") + allConfig, err := k.getKernelConfig() + if err != nil { + return fmt.Errorf("failed to parse kernel config: %v", err) + } + 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 resultType) { + if result == bad { + badConfigs = append(badConfigs, name) + } + // report description when the config is bad or warn. + if result != good && desc != "" { + msg = msg + " - " + desc + } + report(name, msg, result) + } + const ( + required = iota + optional + forbidden + ) + validateOpt := func(config KernelConfig, expect int) { + var found, missing resultType + 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 fmt.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", + } + 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. + // TODO(random-liu): Remove "sudo" in containerize test PR #31093 + output, err := exec.Command("sudo", modprobeCmd, configsModule).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("unable to load kernel module %q: output - %q, err - %v", + configsModule, output, err) + } + // Unload the kernel config module to make sure the validation have no side effect. + defer exec.Command("sudo", modprobeCmd, "-r", configsModule).Run() + loadModule = true + } + return nil, fmt.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 { + glog.Errorf("Unexpected fields number in config %q", line) + continue + } + config[fields[0]] = kConfigOption(fields[1]) + } + return config, nil + +} diff --git a/test/e2e_node/system/os_validator.go b/test/e2e_node/system/os_validator.go new file mode 100644 index 00000000000..fc228a3418c --- /dev/null +++ b/test/e2e_node/system/os_validator.go @@ -0,0 +1,48 @@ +/* +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 ( + "fmt" + "os/exec" + "strings" +) + +var _ Validator = &OSValidator{} + +type OSValidator struct{} + +func (c *OSValidator) Name() string { + return "os" +} + +func (c *OSValidator) Validate(spec SysSpec) error { + out, err := exec.Command("uname").CombinedOutput() + if err != nil { + return fmt.Errorf("failed to get os name: %v", err) + } + return c.validateOS(strings.TrimSpace(string(out)), spec.OS) +} + +func (c *OSValidator) validateOS(os, specOS string) error { + if os != specOS { + report("OS", os, bad) + return fmt.Errorf("unsupported operating system: %s", os) + } + report("OS", os, good) + return nil +} diff --git a/test/e2e_node/system/types.go b/test/e2e_node/system/types.go new file mode 100644 index 00000000000..79e0d779852 --- /dev/null +++ b/test/e2e_node/system/types.go @@ -0,0 +1,122 @@ +/* +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 + +// KernelConfig defines one kernel configration item. +type KernelConfig struct { + // Name is the general name of the kernel configuration. It is used to + // match kernel configuration. + Name string + // Aliases are aliases of the kernel configuration. Some configuration + // has different names in different kernel version. Names of different + // versions will be treated as aliases. + Aliases []string + // Description is the description of the kernel configuration, for example: + // * What is it used for? + // * Why is it needed? + // * Who needs it? + Description string +} + +// KernelSpec defines the specification for the kernel. Currently, it contains +// specification for: +// * Kernel Version +// * Kernel Configuration +type KernelSpec struct { + // Versions define supported kernel version. It is a group of regexps. + Versions []string + // Required contains all kernel configurations required to be enabled + // (built in or as module). + Required []KernelConfig + // Optional contains all kernel configurations are required for optional + // features. + Optional []KernelConfig + // Forbidden contains all kernel configurations which areforbidden (disabled + // or not set) + Forbidden []KernelConfig +} + +// DockerSpec defines the requirement configuration for docker. Currently, it only +// contains spec for graph driver. +type DockerSpec struct { + // Version is a group of regex matching supported docker versions. + Version []string + // GraphDriver is the graph drivers supported by kubelet. + GraphDriver []string +} + +// RuntimeSpec is the abstract layer for different runtimes. Different runtimes +// should put their spec inside the RuntimeSpec. +type RuntimeSpec struct { + *DockerSpec +} + +// SysSpec defines the requirement of supported system. Currently, it only contains +// spec for OS, Kernel and Cgroups. +type SysSpec struct { + // OS is the operating system of the SysSpec. + OS string + // KernelConfig defines the spec for kernel. + KernelSpec KernelSpec + // Cgroups is the required cgroups. + Cgroups []string + // RuntimeSpec defines the spec for runtime. + RuntimeSpec RuntimeSpec +} + +// DefaultSysSpec is the default SysSpec. +var DefaultSysSpec = SysSpec{ + OS: "Linux", + KernelSpec: KernelSpec{ + Versions: []string{`3\.[1-9][0-9].*`, `4\..*`}, // Requires 3.10+ or 4+ + // TODO(random-liu): Add more config + // TODO(random-liu): Add description for each kernel configuration: + Required: []KernelConfig{ + {Name: "NAMESPACES"}, + {Name: "NET_NS"}, + {Name: "PID_NS"}, + {Name: "IPC_NS"}, + {Name: "UTS_NS"}, + {Name: "CGROUPS"}, + {Name: "CGROUP_CPUACCT"}, + {Name: "CGROUP_DEVICE"}, + {Name: "CGROUP_FREEZER"}, + {Name: "CGROUP_SCHED"}, + {Name: "CPUSETS"}, + {Name: "MEMCG"}, + {Name: "INET"}, + {Name: "EXT4_FS"}, + {Name: "PROC_FS"}, + {Name: "NETFILTER_XT_TARGET_REDIRECT", Aliases: []string{"IP_NF_TARGET_REDIRECT"}}, + {Name: "NETFILTER_XT_MATCH_COMMENT"}, + }, + Optional: []KernelConfig{ + {Name: "OVERLAY_FS", Aliases: []string{"OVERLAYFS_FS"}, Description: "Required for overlayfs."}, + {Name: "AUFS_FS", Description: "Required for aufs."}, + {Name: "BLK_DEV_DM", Description: "Required for devicemapper."}, + }, + Forbidden: []KernelConfig{}, + }, + Cgroups: []string{"cpu", "cpuacct", "cpuset", "devices", "freezer", "memory"}, + RuntimeSpec: RuntimeSpec{ + DockerSpec: &DockerSpec{ + Version: []string{`1\.(9|\d{2,})\..*`}, // Requires 1.9+ + // TODO(random-liu): Validate overlay2. + GraphDriver: []string{"aufs", "overlay", "devicemapper"}, + }, + }, +} diff --git a/test/e2e_node/system/util.go b/test/e2e_node/system/util.go new file mode 100644 index 00000000000..612891fcac2 --- /dev/null +++ b/test/e2e_node/system/util.go @@ -0,0 +1,62 @@ +/* +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 ( + "fmt" +) + +// resultType is type of the validation result. Different validation results +// corresponds to different colors. +type resultType int + +const ( + good resultType = iota + bad + warn +) + +// color is the color of the message. +type color int + +const ( + red color = 31 + green = 32 + yellow = 33 + white = 37 +) + +func wrap(s string, c color) string { + return fmt.Sprintf("\033[0;%dm%s\033[0m", c, s) +} + +// report reports "item: r". item is white, and the color of r depends on the +// result type. +func report(item, r string, t resultType) { + var c color + switch t { + case good: + c = green + case bad: + c = red + case warn: + c = yellow + default: + c = white + } + fmt.Printf("%s: %s\n", wrap(item, white), wrap(r, c)) +} diff --git a/test/e2e_node/system/validators.go b/test/e2e_node/system/validators.go new file mode 100644 index 00000000000..c799f8ff34d --- /dev/null +++ b/test/e2e_node/system/validators.go @@ -0,0 +1,49 @@ +/* +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 ( + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/errors" +) + +// Validator is the interface for all validators. +type Validator interface { + // Name is the name of the validator. + Name() string + // Validate is the validate function. + Validate(SysSpec) error +} + +// validators are all the validators. +var validators = []Validator{ + &OSValidator{}, + &KernelValidator{}, + &CgroupsValidator{}, + &DockerValidator{}, +} + +// Validate uses all validators to validate the system. +func Validate() error { + var errs []error + spec := DefaultSysSpec + for _, v := range validators { + glog.Infof("Validating %s...", v.Name()) + errs = append(errs, v.Validate(spec)) + } + return errors.NewAggregate(errs) +}