Add a reporter to the system verification check

This commit is contained in:
Lucas Käldström 2016-11-12 16:36:40 +02:00
parent 3509419543
commit dacec687a4
12 changed files with 96 additions and 51 deletions

View File

@ -93,7 +93,7 @@ func TestE2eNode(t *testing.T) {
glog.Exitf("chroot %q failed: %v", rootfs, err) 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) glog.Exitf("system validation failed: %v", err)
} }
return return

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,20 +18,22 @@ package system
import ( import (
"fmt" "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. // corresponds to different colors.
type resultType int type ValidationResultType int32
const ( const (
good resultType = iota good ValidationResultType = iota
bad bad
warn warn
) )
// color is the color of the message. // color is the color of the message.
type color int type color int32
const ( const (
red color = 31 red color = 31
@ -40,15 +42,19 @@ const (
white = 37 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) 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 // The default reporter for the system verification test
// result type. type StreamReporter struct {
func report(item, r string, t resultType) { // The stream that this reporter is writing to
WriteStream io.Writer
}
func (dr *StreamReporter) Report(key, value string, resultType ValidationResultType) error {
var c color var c color
switch t { switch resultType {
case good: case good:
c = green c = green
case bad: case bad:
@ -58,5 +64,15 @@ func report(item, r string, t resultType) {
default: default:
c = white 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 Validate(SysSpec) error
} }
// validators are all the validators. // Reporter is the interface for the reporters for the validators.
var validators = []Validator{ type Reporter interface {
&OSValidator{}, // Report reports the results of the system verification
&KernelValidator{}, Report(string, string, ValidationResultType) error
&CgroupsValidator{},
&DockerValidator{},
} }
// Validate uses all validators to validate the system. // Validate uses all validators to validate the system.
func Validate() error { func Validate(spec SysSpec, report Reporter) error {
var errs []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 { for _, v := range validators {
glog.Infof("Validating %s...", v.Name()) glog.Infof("Validating %s...", v.Name())
errs = append(errs, v.Validate(spec)) errs = append(errs, v.Validate(spec))
} }
return errors.NewAggregate(errs) return errors.NewAggregate(errs)
} }
// ValidateDefault uses all default validators to validate the system and writes to stdout.
func ValidateDefault() error {
return Validate(DefaultSysSpec, DefaultReporter)
}