-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the 'prometheus_textfile` option.
- When a file is passed, netbackup will generate a node-exporter compatible textfile. This allows easy reporting and alerting of backup conditions.
- Loading branch information
1 parent
e232878
commit 030b575
Showing
5 changed files
with
246 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// netbackup - Consistent multi-method backup tool | ||
// | ||
// See instructions in the README.md file that accompanies this program. | ||
// | ||
// (C) 2015-2024 by Marco Paganini <paganini AT paganini DOT net> | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
// writeNodeTextFile writes a record in a prometheus node-exporter | ||
// compatible "textfile" format. The record is formatted as: | ||
// | ||
// backup{name="foobar", job="netbackup", status="success"} <timestamp> | ||
// | ||
// Existing lines with the same format and name will be overwritten. | ||
// All other lines will remain intact. | ||
// | ||
// The function employs FLock() on a separate lockfile to prevent race | ||
// conditions when modifying to the original file. All writes go into a | ||
// temporary file that is atomically renamed to the final name once work is | ||
// done. | ||
func writeNodeTextFile(filename string, name string) error { | ||
lockfile := filename + ".lock" | ||
lock, err := os.Create(lockfile) | ||
if err != nil { | ||
return err | ||
} | ||
defer lock.Close() | ||
|
||
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil { | ||
return err | ||
} | ||
defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN) | ||
|
||
// Read contents from original filename. | ||
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
data, err := io.ReadAll(file) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Rebuild output without any previous lines with the same name | ||
// and the new line added with the current unix timestamp. | ||
matchname, err := regexp.Compile(`backup[\s]*{.*name="` + name + `".*`) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
output := []byte{} | ||
for _, line := range bytes.Split(data, []byte("\n")) { | ||
// See https://github.com/golang/go/issues/35130 | ||
// To understand why this BS is needed here. | ||
if len(line) == 0 { | ||
continue | ||
} | ||
// Don't copy our own lines. | ||
if matchname.Match(line) { | ||
continue | ||
} | ||
output = append(output, line...) | ||
output = append(output, byte('\n')) | ||
} | ||
// Add our line. | ||
now := time.Now().Unix() | ||
s := fmt.Sprintf("backup{name=%q, job=\"netbackup\", status=\"success\"} %d\n", name, now) | ||
output = append(output, []byte(s)...) | ||
|
||
// Write to temporary file and rename it to the original file name. | ||
dirname, fname := filepath.Split(filename) | ||
if dirname == "" { | ||
dirname = "./" | ||
} | ||
|
||
temp, err := os.CreateTemp(dirname, fname) | ||
if err != nil { | ||
return err | ||
} | ||
defer os.Remove(temp.Name()) | ||
defer temp.Close() | ||
|
||
_, err = temp.Write(output) | ||
if err != nil { | ||
return err | ||
} | ||
temp.Close() | ||
|
||
if err := os.Rename(temp.Name(), filename); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// netbackup - Consistent multi-method backup tool | ||
// | ||
// See instructions in the README.md file that accompanies this program. | ||
// | ||
// (C) 2015-2024 by Marco Paganini <paganini AT paganini DOT net> | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
// Number of records to create/test. | ||
const numRecords = 20 | ||
|
||
// generate creates multiple node compatible backup records in parallel. | ||
func generate(tmpfile string, ch chan error) { | ||
// Generate multiple backup records. | ||
for i := 0; i < numRecords; i++ { | ||
go func(ch chan error, name string) { | ||
err := writeNodeTextFile(tmpfile, name) | ||
ch <- err | ||
}(ch, fmt.Sprintf("backup%03.3d", i)) | ||
} | ||
} | ||
|
||
// errcheck returns the first error found in a slice of error channels. | ||
func errcheck(ch chan error) error { | ||
var saved error | ||
for i := 0; i < numRecords; i++ { | ||
err := <-ch | ||
if err != nil && saved != nil { | ||
saved = err | ||
} | ||
} | ||
return saved | ||
} | ||
|
||
// filecheck parses the generated file and makes sure we have exactly | ||
// numRecords properly formatted records. | ||
func filecheck(t *testing.T, tmpfile string) error { | ||
data, err := os.ReadFile(tmpfile) | ||
if err != nil { | ||
return err | ||
} | ||
lines := bytes.Split(data, []byte("\n")) | ||
|
||
t.Log("Generated file contents") | ||
for i, v := range lines { | ||
t.Logf("%d: %s\n", i, v) | ||
} | ||
|
||
// Make sure we have exactly numRecords lines. | ||
numlines := len(lines) - 1 // Skip the last blank line caused by a newline. | ||
if numlines != numRecords { | ||
return fmt.Errorf("number of lines mismatch: expected %d, found %d", numRecords, numlines) | ||
} | ||
|
||
// Fill in the "names" map with all names found in the file. | ||
re := regexp.MustCompile(`backup[\s]*{name="([^"]*)", job="netbackup", status="success"} [0-9]+`) | ||
names := map[string]bool{} | ||
for _, line := range lines { | ||
// Skip blank line at the end. | ||
if len(line) == 0 { | ||
continue | ||
} | ||
matches := re.FindSubmatch(line) | ||
if matches != nil { | ||
names[string(matches[1])] = true | ||
} | ||
} | ||
|
||
// Make sure all names are present. | ||
missing := []string{} | ||
for i := 0; i < numRecords; i++ { | ||
name := fmt.Sprintf("backup%03.3d", i) | ||
_, ok := names[name] | ||
if !ok { | ||
missing = append(missing, name) | ||
} | ||
} | ||
if len(missing) > 0 { | ||
return fmt.Errorf("missing backup names in output: %s", strings.Join(missing, ", ")) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func TestMulti(t *testing.T) { | ||
ch := make(chan error, numRecords) | ||
|
||
tmpfile := filepath.Join(t.TempDir(), "testfile") | ||
generate(tmpfile, ch) | ||
|
||
if err := errcheck(ch); err != nil { | ||
t.Errorf("TestMulti: error writing textfile: %v", err) | ||
} | ||
if err := filecheck(t, tmpfile); err != nil { | ||
t.Errorf("TestMulti: file contents error: %v", err) | ||
} | ||
} |