diff --git a/CHANGELOG.md b/CHANGELOG.md index 0716b33..780f961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v1.7.0 (12.09.2018) + +### Enhancements + +* New `unmount-volume` command checks if Mesos agent had problems unmounting local persistent volumes. +* New `FindFirstLine` function helps to search in logs. + +### Bug fixes + +* Bun launches a random subcommand instead of the specified one. + +Kudos to Jan and Marvin for sponsoring this release with their company. + ## v1.6.0 (10.09.2018) ### Enhancements diff --git a/bun/cmd/import.go b/bun/cmd/import.go index 2ba5513..754b9c7 100644 --- a/bun/cmd/import.go +++ b/bun/cmd/import.go @@ -4,8 +4,10 @@ import ( _ "github.com/adyatlov/bun/check/dcosversion" _ "github.com/adyatlov/bun/check/health" _ "github.com/adyatlov/bun/check/mesos/actormailboxes" + _ "github.com/adyatlov/bun/check/mesos/unmountvolume" _ "github.com/adyatlov/bun/check/nodecount" _ "github.com/adyatlov/bun/file/dcosversionfile" _ "github.com/adyatlov/bun/file/healthfile" _ "github.com/adyatlov/bun/file/mesos/actormailboxesfile" + _ "github.com/adyatlov/bun/file/mesos/agentlog" ) diff --git a/bun/cmd/root.go b/bun/cmd/root.go index 6c5cd3e..fddb6a0 100644 --- a/bun/cmd/root.go +++ b/bun/cmd/root.go @@ -36,21 +36,22 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&printLong, "long", "l", false, "print details") // Adding registered checks as commands. - for _, check := range bun.Checks() { + for _, c := range bun.Checks() { run := func(cmd *cobra.Command, args []string) { + check := bun.GetCheck(cmd.Use) check.Run(*bundle) printReport(check) return } var cmd = &cobra.Command{ - Use: check.Name, - Short: check.Description, - Long: check.Description, + Use: c.Name, + Short: c.Description, + Long: c.Description, PreRun: preRun, Run: run, } rootCmd.AddCommand(cmd) - rootCmd.ValidArgs = append(rootCmd.ValidArgs, check.Name) + rootCmd.ValidArgs = append(rootCmd.ValidArgs, cmd.Use) rootCmd.PreRun = preRun } } diff --git a/check/mesos/unmountvolume/unmount_volume.go b/check/mesos/unmountvolume/unmount_volume.go new file mode 100644 index 0000000..db02be3 --- /dev/null +++ b/check/mesos/unmountvolume/unmount_volume.go @@ -0,0 +1,30 @@ +package unmountvolume + +import ( + "fmt" + + "github.com/adyatlov/bun" +) + +func init() { + builder := bun.CheckBuilder{ + Name: "unmount-volume", + Description: "Checks if Mesos agents had problems unmounting local persistent volumes", + ForEachAgent: check, + ForEachPublicAgent: check, + } + builder.BuildAndRegister() +} + +func check(host bun.Host) (ok bool, details interface{}, err error) { + line, n, err := host.FindFirstLine("mesos-agent-log", "Failed to destroy nested containers") + if err != nil { + return + } + if n != 0 { + details = fmt.Sprintf("%v: %v", n, line) + return + } + ok = true + return +} diff --git a/file/mesos/agentlog/agent_log.go b/file/mesos/agentlog/agent_log.go new file mode 100644 index 0000000..6531aa2 --- /dev/null +++ b/file/mesos/agentlog/agent_log.go @@ -0,0 +1,17 @@ +package agentlog + +import "github.com/adyatlov/bun" + +func init() { + f := bun.FileType{ + Name: "mesos-agent-log", + ContentType: bun.Journal, + Paths: []string{ + "dcos-mesos-slave.service", + "dcos-mesos-slave-public.service", + }, + Description: "Mesos agent jounrald log", + HostTypes: map[bun.HostType]struct{}{bun.Agent: {}, bun.PublicAgent: {}}, + } + bun.RegisterFileType(f) +} diff --git a/file_owner.go b/file_owner.go index 9adb399..863312c 100644 --- a/file_owner.go +++ b/file_owner.go @@ -1,6 +1,7 @@ package bun import ( + "bufio" "compress/gzip" "encoding/json" "errors" @@ -17,6 +18,21 @@ type fileOwner struct { Path string } +type bulkCloser []io.Closer + +func (bc bulkCloser) Close() error { + e := []string{} + for _, c := range bc { + if err := c.Close(); err != nil { + e = append(e, err.Error()) + } + } + if len(e) > 0 { + return errors.New(strings.Join(e, "\n")) + } + return nil +} + // OpenFile opens the files of the typeName file type. // If the file is not found, it tries to open it from a correspondent .gzip archive. // If the .gzip archive is not found as well then returns an error. @@ -45,6 +61,7 @@ func (fo fileOwner) OpenFile(typeName string) (File, error) { notFound = append(notFound, filePath) continue // not found } + // found r, err := gzip.NewReader(file) if err != nil { return nil, err // error @@ -52,7 +69,7 @@ func (fo fileOwner) OpenFile(typeName string) (File, error) { return struct { io.Reader io.Closer - }{io.Reader(r), bulkCloser{r, file}}, nil // found + }{io.Reader(r), bulkCloser{r, file}}, nil } return nil, fmt.Errorf("none of the following files are found:\n%v", strings.Join(notFound, "\n")) @@ -78,17 +95,27 @@ func (fo fileOwner) ReadJSON(typeName string, v interface{}) error { return json.Unmarshal(data, v) } -type bulkCloser []io.Closer +func (fo fileOwner) FindFirstLine(typeName string, substr string) (l string, n int, err error) { + file, err := fo.OpenFile(typeName) + if err != nil { + return + } + return findFirstLine(file, substr) +} -func (bc bulkCloser) Close() error { - e := []string{} - for _, c := range bc { - if err := c.Close(); err != nil { - e = append(e, err.Error()) +func findFirstLine(r io.Reader, substr string) (l string, n int, err error) { + scanner := bufio.NewScanner(r) + for i := 1; scanner.Scan(); i++ { + line := scanner.Text() + if strings.Contains(line, substr) { + l = line + n = i + return } } - if len(e) > 0 { - return errors.New(strings.Join(e, "\n")) + if err = scanner.Err(); err != nil { + return } - return nil + // Not found + return } diff --git a/file_owner_test.go b/file_owner_test.go new file mode 100644 index 0000000..33a3db8 --- /dev/null +++ b/file_owner_test.go @@ -0,0 +1,28 @@ +package bun + +import ( + "strings" + "testing" +) + +const str = `“Would you tell me, please, which way I ought to go from here?” +“That depends a good deal on where you want to get to,” said the Cat. +“I don’t much care where-–” said Alice. +“Then it doesn’t matter which way you go,” said the Cat. +“-–so long as I get SOMEWHERE,” Alice added as an explanation. +“Oh, you’re sure to do that,” said the Cat, “if you only walk long enough.”` + +func TestFindFirstLine(t *testing.T) { + r := strings.NewReader(str) + line, n, err := findFirstLine(r, "SOMEWHERE") + const expected = `“-–so long as I get SOMEWHERE,” Alice added as an explanation.` + if line != expected { + t.Errorf("Epected line = \"%v\", observed \"%v\"", expected, line) + } + if n != 5 { + t.Errorf("Expected n = 5, observed n = %v", n) + } + if err != nil { + t.Errorf("Expected err = nil, observed err = %v", err) + } +}