Merge pull request #36334 from luxas/add_preflight

Automatic merge from submit-queue

Add the system verification test to the kubeadm preflight checks

And refactor the system verification test to accept to write to a specific writer in order to customize the output

This PR is targeting v1.5, PTAL
cc @Random-Liu @dchen1107 @kubernetes/sig-cluster-lifecycle
pull/6/head
Kubernetes Submit Queue 2016-12-01 04:52:07 -08:00 committed by GitHub
commit 2fab199390
14 changed files with 118 additions and 53 deletions

View File

@ -19,6 +19,7 @@ go_library(
"//pkg/api/validation:go_default_library",
"//pkg/util/initsystem:go_default_library",
"//pkg/util/node:go_default_library",
"//test/e2e_node/system:go_default_library",
],
)

View File

@ -17,6 +17,7 @@ limitations under the License.
package preflight
import (
"bufio"
"errors"
"fmt"
"io"
@ -29,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/util/initsystem"
"k8s.io/kubernetes/pkg/util/node"
"k8s.io/kubernetes/test/e2e_node/system"
)
type PreFlightError struct {
@ -213,9 +215,26 @@ func (hst HttpProxyCheck) Check() (warnings, errors []error) {
return nil, nil
}
type SystemVerificationCheck struct{}
func (sysver SystemVerificationCheck) Check() (warnings, errors []error) {
// Create a buffered writer and choose a quite large value (1M) and suppose the output from the system verification test won't exceed the limit
bufw := bufio.NewWriterSize(os.Stdout, 1*1024*1024)
// Run the system verification check, but write to out buffered writer instead of stdout
err := system.Validate(system.DefaultSysSpec, &system.StreamReporter{WriteStream: bufw})
if err != nil {
// Only print the output from the system verification check if the check failed
fmt.Println("System verification failed. Printing the output from the verification...")
bufw.Flush()
return nil, []error{err}
}
return nil, nil
}
func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
// TODO: Some of these ports should come from kubeadm config eventually:
checks := []PreFlightCheck{
SystemVerificationCheck{},
IsRootCheck{root: true},
HostnameCheck{},
ServiceCheck{Service: "kubelet"},
@ -249,8 +268,8 @@ func RunInitMasterChecks(cfg *kubeadmapi.MasterConfiguration) error {
}
func RunJoinNodeChecks(cfg *kubeadmapi.NodeConfiguration) error {
// TODO: Some of these ports should come from kubeadm config eventually:
checks := []PreFlightCheck{
SystemVerificationCheck{},
IsRootCheck{root: true},
HostnameCheck{},
ServiceCheck{Service: "docker"},

View File

@ -92,7 +92,7 @@ func TestE2eNode(t *testing.T) {
glog.Exitf("chroot %q failed: %v", rootfs, err)
}
}
if err := system.Validate(); err != nil {
if err := system.ValidateDefault(); err != nil {
glog.Exitf("system validation failed: %v", err)
}
return

View File

@ -17,8 +17,8 @@ go_library(
"docker_validator.go",
"kernel_validator.go",
"os_validator.go",
"report.go",
"types.go",
"util.go",
"validators.go",
],
tags = ["automanaged"],

View File

@ -25,7 +25,9 @@ import (
var _ Validator = &CgroupsValidator{}
type CgroupsValidator struct{}
type CgroupsValidator struct {
Reporter Reporter
}
func (c *CgroupsValidator) Name() string {
return "cgroups"
@ -55,9 +57,9 @@ func (c *CgroupsValidator) validateCgroupSubsystems(cgroupSpec, subsystems []str
}
item := cgroupsConfigPrefix + strings.ToUpper(cgroup)
if found {
report(item, "enabled", good)
c.Reporter.Report(item, "enabled", good)
} else {
report(item, "missing", bad)
c.Reporter.Report(item, "missing", bad)
missing = append(missing, cgroup)
}
}

View File

@ -23,7 +23,9 @@ import (
)
func TestValidateCgroupSubsystem(t *testing.T) {
v := &CgroupsValidator{}
v := &CgroupsValidator{
Reporter: DefaultReporter,
}
cgroupSpec := []string{"system1", "system2"}
for desc, test := range map[string]struct {
cgroupSpec []string

View File

@ -28,7 +28,9 @@ import (
var _ Validator = &DockerValidator{}
// DockerValidator validates docker configuration.
type DockerValidator struct{}
type DockerValidator struct {
Reporter Reporter
}
func (d *DockerValidator) Name() string {
return "docker"
@ -63,22 +65,22 @@ func (d *DockerValidator) validateDockerInfo(spec *DockerSpec, info types.Info)
for _, v := range spec.Version {
r := regexp.MustCompile(v)
if r.MatchString(info.ServerVersion) {
report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
d.Reporter.Report(dockerConfigPrefix+"VERSION", info.ServerVersion, good)
matched = true
}
}
if !matched {
report(dockerConfigPrefix+"VERSION", info.ServerVersion, bad)
d.Reporter.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)
for _, gd := range spec.GraphDriver {
if info.Driver == gd {
d.Reporter.Report(item, info.Driver, good)
return nil
}
}
report(item, info.Driver, bad)
d.Reporter.Report(item, info.Driver, bad)
return fmt.Errorf("unsupported graph driver: %s", info.Driver)
}

View File

@ -24,7 +24,9 @@ import (
)
func TestValidateDockerInfo(t *testing.T) {
v := &DockerValidator{}
v := &DockerValidator{
Reporter: DefaultReporter,
}
spec := &DockerSpec{
Version: []string{`1\.(9|\d{2,})\..*`},
GraphDriver: []string{"driver_1", "driver_2"},

View File

@ -39,6 +39,7 @@ var _ Validator = &KernelValidator{}
// and kernel configuration.
type KernelValidator struct {
kernelRelease string
Reporter Reporter
}
func (k *KernelValidator) Name() string {
@ -60,11 +61,11 @@ const (
)
func (k *KernelValidator) Validate(spec SysSpec) error {
out, err := exec.Command("uname", "-r").CombinedOutput()
release, 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))
k.kernelRelease = strings.TrimSpace(string(release))
var errs []error
errs = append(errs, k.validateKernelVersion(spec.KernelSpec))
errs = append(errs, k.validateKernelConfig(spec.KernelSpec))
@ -78,11 +79,11 @@ func (k *KernelValidator) validateKernelVersion(kSpec KernelSpec) error {
for _, versionRegexp := range versionRegexps {
r := regexp.MustCompile(versionRegexp)
if r.MatchString(k.kernelRelease) {
report("KERNEL_VERSION", k.kernelRelease, good)
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, good)
return nil
}
}
report("KERNEL_VERSION", k.kernelRelease, bad)
k.Reporter.Report("KERNEL_VERSION", k.kernelRelease, bad)
return fmt.Errorf("unsupported kernel release: %s", k.kernelRelease)
}
@ -101,7 +102,7 @@ func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfi
badConfigs := []string{}
// reportAndRecord is a helper function to record bad config when
// report.
reportAndRecord := func(name, msg, desc string, result resultType) {
reportAndRecord := func(name, msg, desc string, result ValidationResultType) {
if result == bad {
badConfigs = append(badConfigs, name)
}
@ -109,7 +110,7 @@ func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfi
if result != good && desc != "" {
msg = msg + " - " + desc
}
report(name, msg, result)
k.Reporter.Report(name, msg, result)
}
const (
required = iota
@ -117,7 +118,7 @@ func (k *KernelValidator) validateCachedKernelConfig(allConfig map[string]kConfi
forbidden
)
validateOpt := func(config KernelConfig, expect int) {
var found, missing resultType
var found, missing ValidationResultType
switch expect {
case required:
found, missing = good, bad

View File

@ -24,7 +24,9 @@ import (
)
func TestValidateKernelVersion(t *testing.T) {
v := &KernelValidator{}
v := &KernelValidator{
Reporter: DefaultReporter,
}
// Currently, testRegex is align with DefaultSysSpec.KernelVersion, but in the future
// they may be different.
// This is fine, because the test mainly tests the kernel version validation logic,
@ -69,7 +71,9 @@ func TestValidateKernelVersion(t *testing.T) {
}
func TestValidateCachedKernelConfig(t *testing.T) {
v := &KernelValidator{}
v := &KernelValidator{
Reporter: DefaultReporter,
}
testKernelSpec := KernelSpec{
Required: []KernelConfig{{Name: "REQUIRED_1"}, {Name: "REQUIRED_2", Aliases: []string{"ALIASE_REQUIRED_2"}}},
Optional: []KernelConfig{{Name: "OPTIONAL_1"}, {Name: "OPTIONAL_2"}},
@ -184,7 +188,9 @@ CONFIG_3=n`
"CONFIG_2": asModule,
"CONFIG_3": leftOut,
}
v := &KernelValidator{}
v := &KernelValidator{
Reporter: DefaultReporter,
}
got, err := v.parseKernelConfig(bytes.NewReader([]byte(config)))
assert.Nil(t, err, "Expect error not to occur when parse kernel configuration %q", config)
assert.Equal(t, expected, got)

View File

@ -24,25 +24,27 @@ import (
var _ Validator = &OSValidator{}
type OSValidator struct{}
type OSValidator struct {
Reporter Reporter
}
func (c *OSValidator) Name() string {
func (o *OSValidator) Name() string {
return "os"
}
func (c *OSValidator) Validate(spec SysSpec) error {
out, err := exec.Command("uname").CombinedOutput()
func (o *OSValidator) Validate(spec SysSpec) error {
os, 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)
return o.validateOS(strings.TrimSpace(string(os)), spec.OS)
}
func (c *OSValidator) validateOS(os, specOS string) error {
func (o *OSValidator) validateOS(os, specOS string) error {
if os != specOS {
report("OS", os, bad)
o.Reporter.Report("OS", os, bad)
return fmt.Errorf("unsupported operating system: %s", os)
}
report("OS", os, good)
o.Reporter.Report("OS", os, good)
return nil
}

View File

@ -23,7 +23,9 @@ import (
)
func TestValidateOS(t *testing.T) {
v := &OSValidator{}
v := &OSValidator{
Reporter: DefaultReporter,
}
specOS := "Linux"
for _, test := range []struct {
os string

View File

@ -18,20 +18,22 @@ package system
import (
"fmt"
"io"
"os"
)
// resultType is type of the validation result. Different validation results
// ValidationResultType is type of the validation result. Different validation results
// corresponds to different colors.
type resultType int
type ValidationResultType int32
const (
good resultType = iota
good ValidationResultType = iota
bad
warn
)
// color is the color of the message.
type color int
type color int32
const (
red color = 31
@ -40,15 +42,19 @@ const (
white = 37
)
func wrap(s string, c color) string {
func colorize(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) {
// The default reporter for the system verification test
type StreamReporter struct {
// The stream that this reporter is writing to
WriteStream io.Writer
}
func (dr *StreamReporter) Report(key, value string, resultType ValidationResultType) error {
var c color
switch t {
switch resultType {
case good:
c = green
case bad:
@ -58,5 +64,15 @@ func report(item, r string, t resultType) {
default:
c = white
}
fmt.Printf("%s: %s\n", wrap(item, white), wrap(r, c))
if dr.WriteStream == nil {
return fmt.Errorf("WriteStream has to be defined for this reporter")
}
fmt.Fprintf(dr.WriteStream, "%s: %s\n", colorize(key, white), colorize(value, c))
return nil
}
// DefaultReporter is the default Reporter
var DefaultReporter = &StreamReporter{
WriteStream: os.Stdout,
}

View File

@ -29,21 +29,31 @@ type Validator interface {
Validate(SysSpec) error
}
// validators are all the validators.
var validators = []Validator{
&OSValidator{},
&KernelValidator{},
&CgroupsValidator{},
&DockerValidator{},
// Reporter is the interface for the reporters for the validators.
type Reporter interface {
// Report reports the results of the system verification
Report(string, string, ValidationResultType) error
}
// Validate uses all validators to validate the system.
func Validate() error {
func Validate(spec SysSpec, report Reporter) error {
var errs []error
spec := DefaultSysSpec
// validators are all the validators.
var validators = []Validator{
&OSValidator{Reporter: report},
&KernelValidator{Reporter: report},
&CgroupsValidator{Reporter: report},
&DockerValidator{Reporter: report},
}
for _, v := range validators {
glog.Infof("Validating %s...", v.Name())
errs = append(errs, v.Validate(spec))
}
return errors.NewAggregate(errs)
}
// ValidateDefault uses all default validators to validate the system and writes to stdout.
func ValidateDefault() error {
return Validate(DefaultSysSpec, DefaultReporter)
}