1
0
Fork 0

Added tests to the main package and completely reworked the code around that

This commit is contained in:
Julien Dessaux 2020-02-24 23:05:45 +01:00
parent 2661ce9a2b
commit cadb15f7af
17 changed files with 285 additions and 239 deletions

21
zabbix/flags.go Normal file
View file

@ -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
}

104
zabbix/main.go Normal file
View file

@ -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"
}

56
zabbix/main_test.go Normal file
View file

@ -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")
}

34
zabbix/statefile.go Normal file
View file

@ -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
}

BIN
zabbix/testdata/bareos-fd-17.2.state vendored Normal file

Binary file not shown.

BIN
zabbix/testdata/bareos-fd-18.2.state vendored Normal file

Binary file not shown.

Binary file not shown.

38
zabbix/workdir.go Normal file
View file

@ -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
}