aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore2
-rw-r--r--README.md2
-rw-r--r--config/config.go21
-rw-r--r--config/init.go16
-rw-r--r--config/statefile.go39
-rw-r--r--config/workdir.go40
-rw-r--r--job/utils.go31
-rw-r--r--job/utils_test.go8
-rw-r--r--main.go110
-rw-r--r--zabbix/flags.go21
-rw-r--r--zabbix/main.go104
-rw-r--r--zabbix/main_test.go56
-rw-r--r--zabbix/statefile.go34
-rw-r--r--zabbix/testdata/bareos-fd-17.2.statebin0 -> 2196 bytes
-rw-r--r--zabbix/testdata/bareos-fd-18.2.statebin0 -> 2196 bytes
-rw-r--r--zabbix/testdata/bareos-fd-18.2.state-with-errorbin0 -> 2196 bytes
-rw-r--r--zabbix/workdir.go38
17 files changed, 284 insertions, 238 deletions
diff --git a/.gitignore b/.gitignore
index 5800ceb..ec82ffd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
.*.sw[a-p]
bareos-zabbix-check
bareos-zabbix-check.spool
-*.state
+zabbix/tmp
diff --git a/README.md b/README.md
index 884189d..d36b922 100644
--- a/README.md
+++ b/README.md
@@ -43,8 +43,6 @@ The common way to run this check is without any argument :
There are several flags available if you need to override the defaults :
- -f string : Force the state file to use, defaults to bareos-fd.9102.state if it exists else bacula-fd.9102.state.
- - -q bool : Suppress all output, suitable to force a silent update of the spool file.
- - -v bool : Activates verbose debugging output, defaults to false.
- -w string : Force the work directory to use, defaults to /var/lib/bareos if it exists else /var/lib/bacula.
## Output
diff --git a/config/config.go b/config/config.go
deleted file mode 100644
index 262a5d2..0000000
--- a/config/config.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package config
-
-// Config object
-type Config struct {
- verbose bool
- quiet bool
- stateFile string
- workDir string
-}
-
-// Verbose gets the verbose field of the configuration
-func (config *Config) Verbose() bool { return config.verbose }
-
-// Quiet gets the quiet field of the configuration
-func (config *Config) Quiet() bool { return config.quiet }
-
-// StateFile gets the stateFile field of the configuration
-func (config *Config) StateFile() string { return config.stateFile }
-
-// WorkDir gets the workDir field of the configuration
-func (config *Config) WorkDir() string { return config.workDir }
diff --git a/config/init.go b/config/init.go
deleted file mode 100644
index f737fb3..0000000
--- a/config/init.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package config
-
-import "flag"
-
-// Init initialises a program config from the command line flags
-func (c *Config) Init() {
- flag.BoolVar(&c.verbose, "v", false, "Activates verbose debugging output, defaults to false.")
- flag.BoolVar(&c.quiet, "q", false, "Suppress all output, suitable to force a silent update of the spool file.")
- flag.StringVar(&c.stateFile, "f", "", "Force the state file to use, defaults to "+bareosStateFile+" if it exists else "+baculaStateFile+".")
- flag.StringVar(&c.workDir, "w", "", "Force the work directory to use, defaults to "+bareosWorkDir+" if it exists else "+baculaWorkDir+".")
-
- // command line arguments parsing
- flag.Parse()
- c.checkWorkDir()
- c.checkStateFile()
-}
diff --git a/config/statefile.go b/config/statefile.go
deleted file mode 100644
index 22fd214..0000000
--- a/config/statefile.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package config
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
-)
-
-const (
- bareosStateFile = "bareos-fd.9102.state"
- baculaStateFile = "bacula-fd.9102.state"
-)
-
-func (c *Config) checkStateFile() {
- // Finds the state file to parse
- if c.stateFile != "" {
- c.stateFile = filepath.Join(c.workDir, c.stateFile)
- info, err := os.Stat(c.stateFile)
- if os.IsNotExist(err) || info.IsDir() {
- fmt.Printf("INFO The state file %s does not exist.\n", c.stateFile)
- os.Exit(0)
- }
- } else {
- c.stateFile = filepath.Join(c.workDir, bareosStateFile)
- info, err := os.Stat(c.stateFile)
- if os.IsNotExist(err) || info.IsDir() {
- c.stateFile = filepath.Join(c.workDir, baculaStateFile)
- info, err = os.Stat(c.stateFile)
- if os.IsNotExist(err) || info.IsDir() {
- fmt.Println("INFO Could not find a suitable state file. Has a job ever run?")
- os.Exit(0)
- }
- }
- }
- if c.verbose {
- log.Println("Using state file ", c.stateFile)
- }
-}
diff --git a/config/workdir.go b/config/workdir.go
deleted file mode 100644
index 208df1a..0000000
--- a/config/workdir.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package config
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
-)
-
-const (
- bareosWorkDir = "/var/lib/bareos"
- baculaWorkDir = "/var/lib/bacula"
-)
-
-// checkWorkDir checks if a work directory is valid
-func (c *Config) checkWorkDir() {
- // Determine the work directory to use.
- if c.workDir != "" {
- info, err := os.Stat(c.workDir)
- if os.IsNotExist(err) || !info.IsDir() {
- fmt.Printf("INFO Invalid work directory %s : it does not exist or is not a directory.\n", c.workDir)
- os.Exit(0)
- }
- } else {
- c.workDir = bareosWorkDir
- info, err := os.Stat(c.workDir)
- if os.IsNotExist(err) || !info.IsDir() {
- c.workDir = baculaWorkDir
- info, err := os.Stat(c.workDir)
- if os.IsNotExist(err) || !info.IsDir() {
- fmt.Println("INFO Could not find a suitable work directory. Is bareos or bacula installed?")
- os.Exit(0)
- }
- }
- }
- c.workDir = filepath.Clean(c.workDir)
- if c.verbose {
- log.Println("Setting work directory to ", c.workDir)
- }
-}
diff --git a/job/utils.go b/job/utils.go
index 33c25cd..a6a2f43 100644
--- a/job/utils.go
+++ b/job/utils.go
@@ -1,25 +1,32 @@
package job
// KeepOldestOnly filters a job list and keeps only the most recent entry for a job name
-func KeepOldestOnly(jobs []Job) []Job {
- tmpMap := make(map[string]Job)
- for _, elt := range jobs {
- prev, exists := tmpMap[elt.Name]
- if !exists || (exists && prev.Timestamp < elt.Timestamp) {
- tmpMap[elt.Name] = elt
+func KeepOldestOnly(jobs []Job) (results []Job) {
+outerLoop:
+ for i := 0; i < len(jobs); i++ {
+ job := jobs[i]
+ for j := 0; j < len(results); j++ {
+ result := results[j]
+ if result.Name == job.Name {
+ continue outerLoop
+ }
}
+ for j := i + 1; j < len(jobs); j++ {
+ sec := jobs[j]
+ if sec.Name == job.Name && sec.Timestamp > job.Timestamp {
+ job = sec
+ }
+ }
+ results = append(results, job)
}
- values := make([]Job, 0, len(tmpMap))
- for _, value := range tmpMap {
- values = append(values, value)
- }
- return values
+ return
}
// KeepSuccessOnly returns only the successful jobs from a job list (suiatble to write a new spool file)
func KeepSuccessOnly(jobs []Job) (result []Job) {
result = make([]Job, 0)
- for _, job := range jobs {
+ for i := 0; i < len(jobs); i++ {
+ job := jobs[i]
if job.Success {
result = append(result, job)
}
diff --git a/job/utils_test.go b/job/utils_test.go
index a3e4dcd..250ae2c 100644
--- a/job/utils_test.go
+++ b/job/utils_test.go
@@ -9,8 +9,13 @@ func TestKeepOldestOnly(t *testing.T) {
emptyList := []Job{}
oneJob := []Job{{Name: "a", Timestamp: 10, Success: true}}
twoJobs := []Job{
+ {Name: "a", Timestamp: 5, Success: true},
{Name: "a", Timestamp: 10, Success: true},
+ }
+ threeJobs := []Job{
{Name: "a", Timestamp: 5, Success: true},
+ {Name: "a", Timestamp: 10, Success: true},
+ {Name: "a", Timestamp: 8, Success: false},
}
type args struct {
jobs []Job
@@ -20,9 +25,10 @@ func TestKeepOldestOnly(t *testing.T) {
args args
want []Job
}{
- {"empty list", args{emptyList}, emptyList},
+ {"empty list", args{emptyList}, nil},
{"one job", args{oneJob}, oneJob},
{"two jobs", args{twoJobs}, oneJob},
+ {"three jobs", args{threeJobs}, oneJob},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/main.go b/main.go
index 62347ab..f55943d 100644
--- a/main.go
+++ b/main.go
@@ -1,114 +1,12 @@
+//+build !test
+
package main
import (
- "bareos-zabbix-check/config"
- "bareos-zabbix-check/job"
- "bareos-zabbix-check/spool"
- "bareos-zabbix-check/state"
+ "bareos-zabbix-check/zabbix"
"fmt"
- "log"
- "os"
- "path/filepath"
- "time"
-)
-
-const (
- spoolFileName = "bareos-zabbix-check.spool"
)
func main() {
- var (
- config config.Config
- errorString string
- missingString string
- )
- config.Init()
- // Open the state file
- stateFile, err := os.Open(config.StateFile())
- if err != nil {
- fmt.Printf("INFO Couldn't open state file : %s", err)
- os.Exit(0)
- }
- defer stateFile.Close()
- // parse the state file
- header, err := state.ParseHeader(stateFile)
- if err != nil {
- fmt.Printf("INFO Could not parse state file header : %s", err)
- os.Exit(0)
- }
- if config.Verbose() {
- log.Printf("Parsed header: %+s\n", header)
- }
- // seek to the job entries in the state file
- offset, err := stateFile.Seek(int64(header.LastJobsAddr), 0)
- if err != nil {
- fmt.Printf("INFO Couldn't seek to jobs position in state file : %s", err)
- }
- if uint64(offset) != header.LastJobsAddr {
- fmt.Print("INFO Truncated state file")
- }
- // Then parse the jobs in the state file
- jobs, err := state.ParseJobs(stateFile)
- if err != nil {
- fmt.Printf("INFO Could not parse jobs in state file : %s", err)
- }
- if config.Verbose() {
- log.Printf("%d jobs found in state file\n", len(jobs))
- for i := 0; i < len(jobs); i++ {
- log.Print(jobs[i])
- }
- }
-
- // 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(config.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(config.WorkDir(), spoolFileName))
- if err == nil {
- defer spoolFile.Close()
- err = spool.Serialize(spoolFile, jobs)
- }
- if err != nil {
- fmt.Printf("AVERAGE: Error saving the spool file : %s\n", err)
- os.Exit(0)
- }
-
- now := uint64(time.Now().Unix())
- // We build the error strings
- for _, job := range jobs {
- 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 != "" {
- fmt.Printf("AVERAGE: %s %s", errorString, missingString)
- if spoolErr != nil {
- fmt.Printf(" additionnal errors: %s", spoolErr)
- }
- } else {
- fmt.Printf("OK")
- }
+ fmt.Print(zabbix.Main())
}
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
--- /dev/null
+++ b/zabbix/testdata/bareos-fd-17.2.state
Binary files 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
--- /dev/null
+++ b/zabbix/testdata/bareos-fd-18.2.state
Binary files 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
--- /dev/null
+++ b/zabbix/testdata/bareos-fd-18.2.state-with-error
Binary files 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
+}