From cadb15f7afb5e7c88667eb4006209efca17744af Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Mon, 24 Feb 2020 23:05:45 +0100 Subject: Added tests to the main package and completely reworked the code around that --- zabbix/flags.go | 21 +++++ zabbix/main.go | 104 ++++++++++++++++++++++++ zabbix/main_test.go | 56 +++++++++++++ zabbix/statefile.go | 34 ++++++++ zabbix/testdata/bareos-fd-17.2.state | Bin 0 -> 2196 bytes zabbix/testdata/bareos-fd-18.2.state | Bin 0 -> 2196 bytes zabbix/testdata/bareos-fd-18.2.state-with-error | Bin 0 -> 2196 bytes zabbix/workdir.go | 38 +++++++++ 8 files changed, 253 insertions(+) create mode 100644 zabbix/flags.go create mode 100644 zabbix/main.go create mode 100644 zabbix/main_test.go create mode 100644 zabbix/statefile.go create mode 100644 zabbix/testdata/bareos-fd-17.2.state create mode 100644 zabbix/testdata/bareos-fd-18.2.state create mode 100644 zabbix/testdata/bareos-fd-18.2.state-with-error create mode 100644 zabbix/workdir.go (limited to 'zabbix') diff --git a/zabbix/flags.go b/zabbix/flags.go new file mode 100644 index 0000000..3e95321 --- /dev/null +++ b/zabbix/flags.go @@ -0,0 +1,21 @@ +package zabbix + +import ( + "flag" +) + +var ( + stateFileName string + workDir string +) + +func processFlags() (err error) { + flag.StringVar(&stateFileName, "f", "", "Force the state file to use, defaults to "+bareosStateFile+" if it exists else "+baculaStateFile+".") + flag.StringVar(&workDir, "w", "", "Force the work directory to use, defaults to "+bareosWorkDir+" if it exists else "+baculaWorkDir+".") + flag.Parse() + err = checkWorkDir() + if err == nil { + err = checkStateFile() + } + return +} diff --git a/zabbix/main.go b/zabbix/main.go new file mode 100644 index 0000000..576fc17 --- /dev/null +++ b/zabbix/main.go @@ -0,0 +1,104 @@ +package zabbix + +import ( + "bareos-zabbix-check/job" + "bareos-zabbix-check/spool" + "bareos-zabbix-check/state" + "fmt" + "os" + "path/filepath" + "time" +) + +const ( + spoolFileName = "bareos-zabbix-check.spool" +) + +var now = uint64(time.Now().Unix()) + +// Main the true main function of this program +func Main() string { + err := processFlags() + if err != nil { + return fmt.Sprintf("INFO Failed to init programm : %s", err) + } + // Open the state file + stateFile, err := os.Open(stateFileName) + if err != nil { + return fmt.Sprintf("INFO Could not open state file : %s", err) + } + defer stateFile.Close() + // parse the state file + header, err := state.ParseHeader(stateFile) + if err != nil { + return fmt.Sprintf("INFO Could not parse state file header : %s", err) + } + // seek to the job entries in the state file + offset, err := stateFile.Seek(int64(header.LastJobsAddr), 0) + if err != nil { + return fmt.Sprintf("INFO Couldn't seek to jobs position in state file : %s", err) + } + if uint64(offset) != header.LastJobsAddr { + return fmt.Sprint("INFO Truncated state file") + } + // Then parse the jobs in the state file + jobs, err := state.ParseJobs(stateFile) + if err != nil { + return fmt.Sprintf("INFO Could not parse jobs in state file : %s", err) + } + + // We will check for errors in loading the spool file only at the end. If all jobs ran successfully without errors + // in the state file and we manage to write a new spool file without errors, then we will ignore any error here to + // avoid false positives during backup bootstrap + // Open the spool file + spoolFile, spoolErr := os.Open(filepath.Join(workDir, spoolFileName)) + var spoolJobs []job.Job + if err == nil { + defer spoolFile.Close() + spoolJobs, spoolErr = spool.Parse(spoolFile) + } + + jobs = job.KeepOldestOnly(append(jobs, spoolJobs...)) + + // we write this new spool + spoolFile, err = os.Create(filepath.Join(workDir, spoolFileName)) + if err == nil { + defer spoolFile.Close() + err = spool.Serialize(spoolFile, jobs) + } + if err != nil { + return fmt.Sprintf("AVERAGE: Error saving the spool file : %s\n", err) + } + + var ( + errorString string + missingString string + ) + // We build the error strings + for i := 0; i < len(jobs); i++ { + job := jobs[i] + if job.Success { + if job.Timestamp < now-24*3600 { + if missingString == "" { + missingString = fmt.Sprintf("missing: %s", job.Name) + } else { + missingString = fmt.Sprintf("%s, %s", missingString, job.Name) + } + } + } else { + if errorString == "" { + errorString = fmt.Sprintf("errors: %s", job.Name) + } else { + errorString = fmt.Sprintf("%s, %s", errorString, job.Name) + } + } + } + // Finally we output + if errorString != "" || missingString != "" { + if spoolErr != nil { + return fmt.Sprintf("AVERAGE: %s %s %s", errorString, missingString, spoolErr) + } + return fmt.Sprintf("AVERAGE: %s %s", errorString, missingString) + } + return "OK" +} diff --git a/zabbix/main_test.go b/zabbix/main_test.go new file mode 100644 index 0000000..c0d6056 --- /dev/null +++ b/zabbix/main_test.go @@ -0,0 +1,56 @@ +package zabbix + +import ( + "flag" + "os" + "path/filepath" + "testing" +) + +func TestMain(t *testing.T) { + os.RemoveAll("tmp") + cwd, _ := os.Getwd() + err := os.MkdirAll("tmp/ok-18.2", 0777) + if err != nil { + t.Skipf("skipping main tests because tmp directory cannot be created : %s", err) + } + os.MkdirAll("tmp/ok-17.2", 0777) + os.MkdirAll("tmp/no_state_file", 0777) + os.MkdirAll("tmp/bacula_auto_detect_failed/var/lib/bacula", 0777) + os.MkdirAll("tmp/bareos_auto_detect_failed/var/lib/bareos", 0777) + os.MkdirAll("tmp/error", 0777) + os.Symlink("../../testdata/bareos-fd-17.2.state", "tmp/ok-17.2/state") + os.Symlink("../../testdata/bareos-fd-18.2.state", "tmp/ok-18.2/state") + os.Symlink("../../testdata/bareos-fd-18.2.state-with-error", "tmp/error/state") + tests := []struct { + name string + timestamp uint64 + rootDir string + args []string + want string + }{ + {"failed bacula_auto_detect", 0, "tmp/bacula_auto_detect_failed", []string{}, "INFO Failed to init programm : Could not find a suitable state file. Has a job ever run?"}, + {"failed bareos_auto_detect", 0, "tmp/bareos_auto_detect_failed", []string{}, "INFO Failed to init programm : Could not find a suitable state file. Has a job ever run?"}, + {"failed auto_detect", 0, "tmp/non_existent", []string{}, "INFO Failed to init programm : Could not find a suitable work directory. Is bareos or bacula installed?"}, + {"no work directory", 0, "tmp", []string{"-w", "/non_existent"}, "INFO Failed to init programm : Invalid work directory /home/julien/git/awh/bareos-zabbix-check/zabbix/tmp/non_existent : it does not exist or is not a directory"}, + {"no state file auto_detect", 0, "tmp", []string{"-w", "/no_state_file"}, "INFO Failed to init programm : Could not find a suitable state file. Has a job ever run?"}, + {"no state file", 0, "tmp", []string{"-w", "/no_state_file", "-f", "test"}, "INFO Failed to init programm : The state file /home/julien/git/awh/bareos-zabbix-check/zabbix/tmp/no_state_file/test does not exist"}, + {"ok bareos 18.2", 1582579731, "tmp/ok-18.2", []string{"-w", "/", "-f", "state"}, "OK"}, + {"ok bareos 17.2", 1582579731, "tmp/ok-17.2", []string{"-w", "/", "-f", "state"}, "OK"}, + {"missing", 1582709331, "tmp/ok-18.2", []string{"-w", "/", "-f", "state"}, "AVERAGE: missing: awhphpipam1_percona_xtrabackup, awhphpipam1_LinuxAll, awhphpipam1_www"}, + {"error", 1582579731, "tmp/error", []string{"-w", "/", "-f", "state"}, "AVERAGE: errors: awhphpipam1_percona_xtrabackup, awhphpipam1_www Corrupted spool file: invalid argument"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + now = tt.timestamp + root = filepath.Join(cwd, tt.rootDir) + flag.CommandLine = flag.NewFlagSet("bareos-zabbix-check", flag.ExitOnError) //flags are now reset + os.Args = append([]string{"bareos-zabbix-check"}, tt.args...) + if got := Main(); got != tt.want { + t.Log(workDir) + t.Errorf("Main() = %v, want %v", got, tt.want) + } + }) + } + os.RemoveAll("tmp") +} diff --git a/zabbix/statefile.go b/zabbix/statefile.go new file mode 100644 index 0000000..26ea650 --- /dev/null +++ b/zabbix/statefile.go @@ -0,0 +1,34 @@ +package zabbix + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + bareosStateFile = "bareos-fd.9102.state" + baculaStateFile = "bacula-fd.9102.state" +) + +func checkStateFile() error { + // Finds the state file to parse + if stateFileName != "" { + stateFileName = filepath.Join(workDir, stateFileName) + info, err := os.Stat(stateFileName) + if os.IsNotExist(err) || info.IsDir() { + return fmt.Errorf("The state file %s does not exist", stateFileName) + } + } else { + stateFileName = filepath.Join(workDir, bareosStateFile) + info, err := os.Stat(stateFileName) + if os.IsNotExist(err) || info.IsDir() { + stateFileName = filepath.Join(workDir, baculaStateFile) + info, err = os.Stat(stateFileName) + if os.IsNotExist(err) || info.IsDir() { + return fmt.Errorf("Could not find a suitable state file. Has a job ever run?") + } + } + } + return nil +} diff --git a/zabbix/testdata/bareos-fd-17.2.state b/zabbix/testdata/bareos-fd-17.2.state new file mode 100644 index 0000000..e98786c Binary files /dev/null and b/zabbix/testdata/bareos-fd-17.2.state differ diff --git a/zabbix/testdata/bareos-fd-18.2.state b/zabbix/testdata/bareos-fd-18.2.state new file mode 100644 index 0000000..ca1f30c Binary files /dev/null and b/zabbix/testdata/bareos-fd-18.2.state differ diff --git a/zabbix/testdata/bareos-fd-18.2.state-with-error b/zabbix/testdata/bareos-fd-18.2.state-with-error new file mode 100644 index 0000000..3d28356 Binary files /dev/null and b/zabbix/testdata/bareos-fd-18.2.state-with-error differ diff --git a/zabbix/workdir.go b/zabbix/workdir.go new file mode 100644 index 0000000..287c80a --- /dev/null +++ b/zabbix/workdir.go @@ -0,0 +1,38 @@ +package zabbix + +import ( + "fmt" + "os" + "path/filepath" +) + +const ( + bareosWorkDir = "/var/lib/bareos" + baculaWorkDir = "/var/lib/bacula" +) + +var root = "/" + +// checkWorkDir checks if a work directory is valid +func checkWorkDir() error { + // Determine the work directory to use. + if workDir != "" { + workDir = filepath.Join(root, workDir) + info, err := os.Stat(workDir) + if os.IsNotExist(err) || !info.IsDir() { + return fmt.Errorf("Invalid work directory %s : it does not exist or is not a directory", workDir) + } + } else { + workDir = filepath.Join(root, bareosWorkDir) + info, err := os.Stat(workDir) + if os.IsNotExist(err) || !info.IsDir() { + workDir = filepath.Join(root, baculaWorkDir) + info, err := os.Stat(workDir) + if os.IsNotExist(err) || !info.IsDir() { + return fmt.Errorf("Could not find a suitable work directory. Is bareos or bacula installed?") + } + } + } + workDir = filepath.Clean(workDir) + return nil +} -- cgit v1.2.3