Add sample for partial GO implementation

This commit is contained in:
Michael Hoess 2020-10-20 16:19:23 +02:00
parent 49270d4d28
commit 352e8cf9b1
1 changed files with 251 additions and 0 deletions

251
zbx-smart.go Normal file
View File

@ -0,0 +1,251 @@
package main
import (
"bufio"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"strings"
)
type SmartDev struct {
DeviceFile string
Name string
Type string
}
// Lower case for zabbix-expected Json
type SmartDiscoEntry struct {
Name string `json:"name"`
Type string `json:"type"`
Model *string `json:"model"`
SN *string `json:"sn"`
Rotations *int32 `json:"rotations"`
Lbs *int32 `json:"lbs"`
IsExternal int32 `json:"isExternal"`
IsSSD int32 `json:"isSSD"`
}
type SmartInfoEntry struct {
Name string
DeviceFile string
Health string
PowerOnHours int32
PowerCycleCount int32
Temperature float32
}
type smartctlJson struct {
ModelName string `json:"model_name"`
SN string `json:"serial_number"`
Rotations int32 `json:"rotation_rate"`
Lbs int32 `json:"logical_block_size"`
}
func ExecSmartCtl(resAsSingleString bool, args ...string) ([]string, error) {
path, err := exec.LookPath("smartctl")
if err != nil {
return nil, fmt.Errorf("coudn't find smartctl %v", err)
}
out, err := exec.Command(path, args...).Output()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() == 2 {
return nil, fmt.Errorf("smartctl failed, not running root?: %v", exitErr)
} else if errors.As(err, &exitErr) && exitErr.ExitCode() == 4 {
// ignore, no or partial smart support
} else {
return nil, fmt.Errorf("smartctl failed: %v", exitErr)
}
}
if !resAsSingleString {
var lns []string
s := bufio.NewScanner(strings.NewReader(string(out)))
for s.Scan() {
lns = append(lns, s.Text())
}
return lns, nil
} else {
var lns []string
lns = append(lns, string(out))
return lns, nil
}
}
func IsUSBDrive(dev string) (bool, error) {
path, err := exec.LookPath("lsblk")
if err != nil {
return false, fmt.Errorf("couldn't find lsblk: %v", err)
}
out, err := exec.Command(path, "-d", dev, "-o", "SUBSYSTEMS").Output()
if err != nil {
return false, fmt.Errorf("calling lsblk failed: %v", err)
}
return strings.Index(string(out), "usb") >= 0, nil
}
func GetSmartDevsFromScan() ([]SmartDev, error) {
var r []SmartDev
lns, err := ExecSmartCtl(false, "-n", "standby", "--scan")
if err != nil {
return r, fmt.Errorf("Scanning for devices: %w", err)
}
for _, ln := range lns {
if len(ln) > 0 {
//fmt.Println("x" + ln)
splt := strings.Split(ln, " ")
var sd SmartDev
sd.DeviceFile = splt[0]
sd.Name = strings.Split(splt[0], "/")[2]
sd.Type = strings.Split(strings.Split(ln, ",")[1], " ")[1]
r = append(r, sd)
}
}
return r, nil
}
func GetSmartDisco() ([]SmartDiscoEntry, error) {
var r []SmartDiscoEntry
devs, err := GetSmartDevsFromScan()
if err != nil {
return r, err
}
for _, dev := range devs {
isUSB, err := IsUSBDrive(dev.DeviceFile)
if err != nil {
return r, fmt.Errorf("failed get disco: %v", err)
}
var entry SmartDiscoEntry
if !isUSB {
//fmt.Println(dev.DeviceFile)
lns, err := ExecSmartCtl(true, "-n", "standby", "-a", dev.DeviceFile, "--json")
if err != nil {
fmt.Fprintf(os.Stderr, "? skipped device %v: %v\n", dev.Name, err)
continue
}
//j := lns[0]
var sc smartctlJson
// Prevent collision with bash-call
err = json.Unmarshal([]byte(strings.ReplaceAll(lns[0], "'", "")), &sc)
if err != nil {
return r, fmt.Errorf("failed parsing json: %v", err)
}
entry.Model = &sc.ModelName
entry.SN = &sc.SN
entry.Rotations = &sc.Rotations
entry.Lbs = &sc.Lbs
} else {
entry.Model = nil
entry.SN = nil
entry.Rotations = nil
entry.Lbs = nil
}
entry.Name = dev.Name
entry.Type = dev.Type
entry.IsExternal = func() int32 {
if isUSB {
return 1
}
return 0
}()
entry.IsSSD = 0 // TODO
r = append(r, entry)
}
return r, nil
}
func SendSmartDiscoToZabbix(testMode bool) error {
disco, err := GetSmartDisco()
if err != nil {
return fmt.Errorf("SendSmartDiscoToZabbix failed: %w", err)
}
j, err := json.Marshal(disco)
if err != nil {
return fmt.Errorf("SendSmartDiscoToZabbix failed: %v", err)
}
if testMode {
fmt.Printf("Testmode: Would send %v\n", string(j))
} else {
path, err := exec.LookPath("bash")
if err != nil {
return fmt.Errorf("coudn't find bash %v", err)
}
_, err = exec.Command(path, "-c", ""+
"zabbix_sender -vv -k 8o_smartcheck.disco_devs -o '"+
string(j)+
"' -c /etc/zabbix/zabbix_agentd.conf"+
" | logger -t zbs_smart").Output()
if err != nil {
return fmt.Errorf("zabbix_sender exec failed: %v", err)
}
}
return nil
}
func main() {
doDiscoPtr := flag.Bool("smart_disco", false, "Do discovery")
doCheckPtr := flag.Bool("smart_check", false, "Do check")
testModePtr := flag.Bool("test", false, "TestMode")
flag.Parse()
var err error
if *testModePtr {
fmt.Println("----- Testing GetSmartDevsFromScan")
s, _ := GetSmartDevsFromScan()
j, _ := json.Marshal(s)
fmt.Println(string(j))
fmt.Println()
fmt.Println("----- Testing IsUsbDrive")
for _, dev := range s {
isUSB, err := IsUSBDrive(dev.DeviceFile)
fmt.Printf("- %s %v %+v\n", dev.Name, isUSB, err)
}
fmt.Println("----- SendSmartDiscoToZabbix")
err = SendSmartDiscoToZabbix(*testModePtr)
}
if *doDiscoPtr {
fmt.Println("Doing SMART device discovery")
err = SendSmartDiscoToZabbix(*testModePtr)
}
if err == nil && *doCheckPtr {
fmt.Println("Doig SMART checks")
// TODO
}
if err != nil {
fmt.Fprintf(os.Stderr, "! %+v\n", err)
}
fmt.Println("Done")
}