refactoring
This commit is contained in:
parent
919aa659cb
commit
cb72be6aca
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
raidcheck
|
raidcheck
|
||||||
|
.env
|
||||||
|
mdstat
|
@ -2,8 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"git.tordarus.net/Tordarus/dockerhealth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func filterChanges(in <-chan RaidState) <-chan RaidState {
|
func filterChanges(in <-chan RaidState) <-chan RaidState {
|
||||||
@ -14,17 +12,12 @@ func filterChanges(in <-chan RaidState) <-chan RaidState {
|
|||||||
|
|
||||||
currentStates := map[string]RaidState{}
|
currentStates := map[string]RaidState{}
|
||||||
|
|
||||||
first := true
|
|
||||||
|
|
||||||
for state := range in {
|
for state := range in {
|
||||||
if oldState, ok := currentStates[state.Name]; !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) {
|
oldState, ok := currentStates[state.Name]
|
||||||
dockerhealth.Healthy = first
|
if !ok || !reflect.DeepEqual(oldState.DevicesUp, state.DevicesUp) {
|
||||||
currentStates[state.Name] = state
|
currentStates[state.Name] = state
|
||||||
out <- state
|
out <- state
|
||||||
} else {
|
|
||||||
dockerhealth.Healthy = true
|
|
||||||
}
|
}
|
||||||
first = false
|
|
||||||
}
|
}
|
||||||
}(in, out)
|
}(in, out)
|
||||||
|
|
||||||
|
6
go.mod
6
go.mod
@ -1,9 +1,9 @@
|
|||||||
module git.tordarus.net/Tordarus/raidcheck
|
module git.milar.in/milarin/raidcheck
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.tordarus.net/Tordarus/adverr v0.2.0
|
git.milar.in/milarin/adverr v1.1.0
|
||||||
git.tordarus.net/Tordarus/dockerhealth v0.0.1
|
git.milar.in/milarin/envvars/v2 v2.0.0
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
)
|
)
|
||||||
|
6
go.sum
6
go.sum
@ -1,5 +1,7 @@
|
|||||||
git.tordarus.net/Tordarus/adverr v0.2.0 h1:kLYjR2/Vb2GHiSAMvAv+WPNaHR9BRphKanf8H/pCZdA=
|
git.milar.in/milarin/adverr v1.1.0 h1:jD9WnOvs40lfMhvqQ7cllOaRJNBMWr1f07/s9jAadp0=
|
||||||
git.tordarus.net/Tordarus/adverr v0.2.0/go.mod h1:XRf0+7nhOkIEr0gi9DUG4RvV2KaOFB0fYPDaR1KLenw=
|
git.milar.in/milarin/adverr v1.1.0/go.mod h1:joU9sBb7ySyNv4SpTXB0Z4o1mjXsArBw4N27wjgzj9E=
|
||||||
|
git.milar.in/milarin/envvars/v2 v2.0.0 h1:DWRQCWaHqzDD8NGpSgv5tYLuF9A/dVFPAtTvz3oiIqE=
|
||||||
|
git.milar.in/milarin/envvars/v2 v2.0.0/go.mod h1:HkdEi+gG2lJSmVq547bTlQV4qQ0hO333bE8IrE0B9yY=
|
||||||
git.tordarus.net/Tordarus/dockerhealth v0.0.1 h1:rxkwmCW5PDe9gbnXap7d3n5rK1Qyr6xpmJPZFom/ZXc=
|
git.tordarus.net/Tordarus/dockerhealth v0.0.1 h1:rxkwmCW5PDe9gbnXap7d3n5rK1Qyr6xpmJPZFom/ZXc=
|
||||||
git.tordarus.net/Tordarus/dockerhealth v0.0.1/go.mod h1:U0IPsBJHAjqWgNyehWwGfYqTwPJgqBNhzk/eBpaZsnE=
|
git.tordarus.net/Tordarus/dockerhealth v0.0.1/go.mod h1:U0IPsBJHAjqWgNyehWwGfYqTwPJgqBNhzk/eBpaZsnE=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
|
41
main.go
41
main.go
@ -3,56 +3,47 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.tordarus.net/Tordarus/adverr"
|
"git.milar.in/milarin/adverr"
|
||||||
"git.tordarus.net/Tordarus/dockerhealth"
|
"git.milar.in/milarin/envvars/v2"
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
interval = 1 * time.Minute
|
MdstatFile = envvars.String("MDSTAT_FILE", "/proc/mdstat")
|
||||||
telegramToken string
|
PollInterval = envvars.Duration("POLL_INTERVAL", time.Minute)
|
||||||
chatID int64
|
BotToken = envvars.String("TELEGRAM_API_TOKEN", "")
|
||||||
|
ChatID = envvars.Int64("TELEGRAM_CHAT_ID", 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if i, ok := os.LookupEnv("POLL_INTERVAL"); ok {
|
if BotToken == "" {
|
||||||
if interval2, err := time.ParseDuration(i); err == nil {
|
fmt.Fprintln(os.Stderr, "$TELEGRAM_API_TOKEN not set")
|
||||||
interval = interval2
|
os.Exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tt, ok := os.LookupEnv("TELEGRAM_API_TOKEN"); ok {
|
if ChatID == 0 {
|
||||||
telegramToken = tt
|
fmt.Fprintln(os.Stderr, "$TELEGRAM_CHAT_ID not set")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id, ok := os.LookupEnv("TELEGRAM_CHAT_ID"); ok {
|
bot, err := tgbotapi.NewBotAPI(BotToken)
|
||||||
if chatId2, err := strconv.Atoi(id); err == nil {
|
|
||||||
chatID = int64(chatId2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bot, err := tgbotapi.NewBotAPI(telegramToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
adverr.Fatalln(err, 1)
|
adverr.Fatalln(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for state := range filterChanges(mdstat()) {
|
for state := range filterChanges(mdstat(MdstatFile, PollInterval)) {
|
||||||
str := state.String()
|
str := state.String()
|
||||||
fmt.Println(str)
|
fmt.Println(str)
|
||||||
message := tgbotapi.NewMessage(chatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str))
|
message := tgbotapi.NewMessage(ChatID, fmt.Sprintf("Der Zustand des RAID Systems %s hat sich verändert:\n\n%s", state.Name, str))
|
||||||
message.Entities = append(message.Entities, tgbotapi.MessageEntity{
|
message.Entities = append(message.Entities, tgbotapi.MessageEntity{
|
||||||
Type: "code",
|
Type: "code",
|
||||||
Offset: len("Der Zustand des RAID Systems %s hat sich verändert:\n\n"),
|
Offset: len("Der Zustand des RAID Systems %s hat sich verändert:\n\n"),
|
||||||
Length: len(str),
|
Length: len(str),
|
||||||
})
|
})
|
||||||
_, err := bot.Send(message)
|
if _, err := bot.Send(message); err != nil {
|
||||||
if err != nil {
|
|
||||||
adverr.Println(err)
|
adverr.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dockerhealth.Healthy = false
|
|
||||||
}
|
}
|
||||||
|
113
mdstat.go
113
mdstat.go
@ -16,59 +16,74 @@ var (
|
|||||||
deviceRegex = regexp.MustCompile(`^([\w\d]+)\[(\d+)\]$`)
|
deviceRegex = regexp.MustCompile(`^([\w\d]+)\[(\d+)\]$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func mdstat() <-chan RaidState {
|
func mdstat(file string, pollInterval time.Duration) <-chan RaidState {
|
||||||
ch := make(chan RaidState, 4)
|
ch := make(chan RaidState, 4)
|
||||||
|
go parseMdstatOutput(file, pollInterval, ch)
|
||||||
go func(out chan<- RaidState) {
|
|
||||||
defer close(ch)
|
|
||||||
|
|
||||||
for {
|
|
||||||
data, err := os.ReadFile("/proc/mdstat")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
|
|
||||||
state := RaidState{}
|
|
||||||
for _, line := range lines {
|
|
||||||
if match := firstLineRegex.FindStringSubmatch(line); match != nil {
|
|
||||||
state.Name = match[1]
|
|
||||||
state.State = match[2]
|
|
||||||
state.Level = match[3]
|
|
||||||
|
|
||||||
devices := strings.Split(match[4], " ")
|
|
||||||
state.Devices = make([]string, len(devices))
|
|
||||||
for _, device := range devices {
|
|
||||||
if match := deviceRegex.FindStringSubmatch(device); match != nil {
|
|
||||||
deviceIndex, _ := strconv.Atoi(match[2])
|
|
||||||
state.Devices[deviceIndex] = match[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if match := secondLineRegex.FindStringSubmatch(line); match != nil {
|
|
||||||
state.RegisteredDeviceCount, _ = strconv.Atoi(match[1])
|
|
||||||
state.UsedDeviceCount, _ = strconv.Atoi(match[2])
|
|
||||||
state.DevicesUp = map[string]bool{}
|
|
||||||
for i, rn := range match[3] {
|
|
||||||
state.DevicesUp[state.Devices[i]] = rn == 'U'
|
|
||||||
}
|
|
||||||
} else if match := actionLineRegex.FindStringSubmatch(line); match != nil {
|
|
||||||
state.Action = new(RaidAction)
|
|
||||||
state.Action.Name = match[1]
|
|
||||||
state.Action.Progress, _ = strconv.ParseFloat(match[2], 64)
|
|
||||||
state.Action.Duration = parseFinished(match[3], match[4])
|
|
||||||
state.Action.DurationFormatted = state.Action.Duration.String()
|
|
||||||
state.Action.Finished = time.Now().Add(state.Action.Duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out <- state
|
|
||||||
time.Sleep(time.Minute)
|
|
||||||
}
|
|
||||||
}(ch)
|
|
||||||
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseMdstatOutput(file string, pollInterval time.Duration, out chan<- RaidState) {
|
||||||
|
defer close(out)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(pollInterval)
|
||||||
|
for range ticker.C {
|
||||||
|
data, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
state := RaidState{}
|
||||||
|
for _, line := range lines {
|
||||||
|
parseLine(&state, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
out <- state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(state *RaidState, line string) {
|
||||||
|
if matches := firstLineRegex.FindStringSubmatch(line); matches != nil {
|
||||||
|
parseFirstLine(state, matches)
|
||||||
|
} else if matches := secondLineRegex.FindStringSubmatch(line); matches != nil {
|
||||||
|
parseSecondLine(state, matches)
|
||||||
|
} else if match := actionLineRegex.FindStringSubmatch(line); match != nil {
|
||||||
|
parseActionLine(state, matches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFirstLine(state *RaidState, matches []string) {
|
||||||
|
state.Name = matches[1]
|
||||||
|
state.State = matches[2]
|
||||||
|
state.Level = matches[3]
|
||||||
|
|
||||||
|
devices := strings.Split(matches[4], " ")
|
||||||
|
state.Devices = make([]string, len(devices))
|
||||||
|
for deviceIndex, device := range devices {
|
||||||
|
if match := deviceRegex.FindStringSubmatch(device); match != nil {
|
||||||
|
state.Devices[deviceIndex] = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSecondLine(state *RaidState, matches []string) {
|
||||||
|
state.RegisteredDeviceCount, _ = strconv.Atoi(matches[1])
|
||||||
|
state.UsedDeviceCount, _ = strconv.Atoi(matches[2])
|
||||||
|
state.DevicesUp = map[string]bool{}
|
||||||
|
for i, rn := range matches[3] {
|
||||||
|
state.DevicesUp[state.Devices[i]] = rn == 'U'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseActionLine(state *RaidState, matches []string) {
|
||||||
|
state.Action = new(RaidAction)
|
||||||
|
state.Action.Name = matches[1]
|
||||||
|
state.Action.Progress, _ = strconv.ParseFloat(matches[2], 64)
|
||||||
|
state.Action.Duration = parseFinished(matches[3], matches[4])
|
||||||
|
state.Action.DurationFormatted = state.Action.Duration.String()
|
||||||
|
state.Action.Finished = time.Now().Add(state.Action.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
func parseFinished(value, unit string) time.Duration {
|
func parseFinished(value, unit string) time.Duration {
|
||||||
switch unit {
|
switch unit {
|
||||||
case "sec":
|
case "sec":
|
||||||
|
Loading…
Reference in New Issue
Block a user