backup/app.go

488 lines
14 KiB
Go
Raw Normal View History

2021-10-16 15:39:54 +02:00
package main
import (
2023-06-29 22:58:24 +02:00
"errors"
2021-10-16 15:39:54 +02:00
"regexp"
2023-06-29 22:58:24 +02:00
"strings"
2021-10-16 15:39:54 +02:00
"time"
2023-06-29 22:58:24 +02:00
log "github.com/sirupsen/logrus"
2021-10-16 15:39:54 +02:00
)
2023-06-29 22:58:24 +02:00
type App struct {
name string
schedule map[string]struct{}
sources []Addr
destinations []Addr
before map[string]Addr
after map[string]Addr
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
func (c *Config) NewApp(name string, sources, destinations, schedule []string, before, after map[string]string) (*App, error) {
log.WithFields(log.Fields{"name": name}).Debugf("starting")
defer log.WithFields(log.Fields{"name": name}).Debugf("done")
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
a := &App{
name: name,
sources: make([]Addr, 0),
destinations: make([]Addr, 0),
schedule: make(map[string]struct{}, 0),
before: make(map[string]Addr),
after: make(map[string]Addr),
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for _, v := range sources {
src := Addr(v)
if src.Box() == "" {
err := errors.New("source box incorrect")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
}
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
if _, ok := c.box[src.Box()]; !ok {
err := errors.New("source box doesn't exist")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
if src.Path() == "" {
err := errors.New("source path incorrect")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
}
2021-11-14 10:20:44 +01:00
2023-06-29 22:58:24 +02:00
a.sources = append(a.sources, src)
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for _, v := range destinations {
dest := Addr(v)
if dest.Box() == "" {
err := errors.New("destination box incorrect")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
if _, ok := c.box[dest.Box()]; !ok {
err := errors.New("destination box doesn't exist")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
if dest.Path() == "" {
err := errors.New("destination path incorrect")
log.WithFields(log.Fields{"app": name, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
a.destinations = append(a.destinations, dest)
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for _, v := range schedule {
switch strings.ToLower(v) {
case "hourly":
a.schedule["hourly"] = struct{}{}
case "daily":
a.schedule["daily"] = struct{}{}
case "weekly":
a.schedule["weekly"] = struct{}{}
case "monthly":
a.schedule["monthly"] = struct{}{}
case "yearly":
a.schedule["yearly"] = struct{}{}
default:
err := errors.New("schedule incorrect")
log.WithFields(log.Fields{"app": name, "schedule": v, "error": err}).Errorf("")
return nil, err
}
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for k, v := range before {
if _, err := regexp.Compile(k); err != nil {
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "call": "regexp.Compile", "attr": k, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
script := Addr(v)
if script.Box() == "" {
err := errors.New("before box incorrect")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
if script.Path() == "" {
err := errors.New("before path incorrect")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
}
if _, ok := a.before[k]; ok {
err := errors.New("before already exists")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
a.before[k] = script
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for k, v := range after {
if _, err := regexp.Compile(k); err != nil {
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "call": "regexp.Compile", "attr": k, "error": err}).Errorf("")
return nil, err
}
script := Addr(v)
if script.Box() == "" {
err := errors.New("after box incorrect")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
}
if script.Path() == "" {
err := errors.New("after path incorrect")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
}
if _, ok := a.before[k]; ok {
err := errors.New("after already exists")
log.WithFields(log.Fields{"app": name, "schedule": k, "addr": v, "error": err}).Errorf("")
return nil, err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
a.after[k] = script
}
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
return a, nil
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
func (a *App) Cleanup(now time.Time) error {
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
for _, src := range a.sources {
for _, s := range cfg.box[src.Box()].zfs.filesystems[src.Path()].snapshots {
2023-07-01 00:15:31 +02:00
if !s.Valid() {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "now": now, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
}
} else if expired, err := s.Expired(now); err != nil {
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"app": a.name, "now": now, "box": src.Box(), "snapshot": s.String(), "call": "Expired", "error": err}).Errorf("")
return err
} else if expired {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "now": now, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
}
}
2023-06-29 22:58:24 +02:00
for _, dest := range a.destinations {
dest = dest.Append("/" + src.Box() + "/" + src.Path())
for _, s := range cfg.box[dest.Box()].zfs.filesystems[dest.Path()].snapshots {
if !s.Valid() {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "now": now, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
}
} else if expired, err := s.Expired(now); err != nil {
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"app": a.name, "now": now, "box": dest.Box(), "snapshot": s.String(), "call": "Expired", "error": err}).Errorf("")
return err
} else if expired {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "now": now, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
}
}
2021-10-16 15:39:54 +02:00
}
}
}
2023-06-29 22:58:24 +02:00
return nil
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
func (a *App) SanityCheck() error {
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
for _, src := range a.sources {
b := cfg.box[src.Box()]
if !b.online {
err := errors.New("source box offline")
log.WithFields(log.Fields{"app": a.name, "box": src.Box(), "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
if _, ok := b.zfs.filesystems[src.Path()]; !ok {
err := errors.New("source path doesn't exist")
log.WithFields(log.Fields{"app": a.name, "box": src.Box(), "path": src.Path(), "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
for _, s := range b.zfs.filesystems[src.Path()].snapshots {
if !s.Valid() {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
}
}
}
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
onlineDestinations := 0
for _, dest := range a.destinations {
b := cfg.box[dest.Box()]
if b.online {
2023-06-29 22:58:24 +02:00
onlineDestinations++
for _, src := range a.sources {
dest2 := dest.Append("/" + src.Box() + "/" + src.Path())
for _, s := range b.zfs.filesystems[dest2.Path()].snapshots {
if !s.Valid() {
if err := s.Delete(); err != nil {
log.WithFields(log.Fields{"app": a.name, "box": src.Box(), "snapshot": s.String(), "call": "Delete", "error": err}).Errorf("")
return err
}
}
}
}
2021-10-16 15:39:54 +02:00
}
}
2023-06-29 22:58:24 +02:00
if onlineDestinations == 0 {
err := errors.New("no destination box online")
log.WithFields(log.Fields{"app": a.name, "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
return nil
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
func (a *App) RunSchedule(schedule string, now time.Time) error {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now}).Debugf("done")
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
snapshotName := SnapshotName(schedule, now)
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "snapshot": snapshotName}).Debugf("snapshot name")
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
if err := a.RunBefore(schedule); err != nil {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "call": "RunBefore", "attr": schedule, "error": err}).Errorf("")
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
for _, src := range a.sources {
srcFs := cfg.box[src.Box()].zfs.filesystems[src.Path()]
if _, err := srcFs.TakeSnapshot(snapshotName); err != nil {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "call": "TakeSnapshot", "attr": snapshotName, "error": err}).Errorf("")
2021-10-16 15:39:54 +02:00
}
}
2023-06-29 22:58:24 +02:00
if err := a.RunAfter(schedule); err != nil {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "now": now, "call": "RunAfter", "attr": schedule, "error": err}).Errorf("")
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
return nil
2023-06-29 22:58:24 +02:00
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
func (a *App) Run(now time.Time) error {
log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("done")
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
if err := a.SanityCheck(); err != nil {
log.WithFields(log.Fields{"app": a.name, "now": now, "call": "SanityCheck", "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
schedule, err := a.NextSchedule(now)
if err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "NextSchedule", "error": err}).Errorf("")
return err
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"app": a.name, "now": now, "schedule": schedule}).Debugf("schedule")
if schedule != "" {
if err := a.RunSchedule(schedule, now); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "NextSchedule", "error": err}).Errorf("")
return err
2021-10-18 16:13:04 +02:00
}
}
2023-06-29 22:58:24 +02:00
if err := a.Transfer(); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "Transfer", "error": err}).Errorf("")
return err
2021-10-18 16:13:04 +02:00
}
2021-10-16 15:39:54 +02:00
2023-06-29 22:58:24 +02:00
if err := a.Cleanup(now); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "Cleanup", "error": err}).Errorf("")
return err
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
return nil
}
2023-06-29 22:58:24 +02:00
func (a *App) NextSchedule(now time.Time) (string, error) {
log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name, "now": now}).Debugf("done")
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
// get a list of all the common timestamps in sources snapshots
snapshots := make(map[string]map[time.Time]int)
for _, v := range a.sources {
log.WithFields(log.Fields{"app": a.name, "now": now, "box": v.Box(), "path": v.Path()}).Debugf("source")
b := cfg.box[v.Box()] // we tested the boxes
fs, ok := b.zfs.filesystems[v.Path()]
if !ok {
err := errors.New("path doesn't exist")
log.WithFields(log.Fields{"app": a.name, "now": now, "box": v.Box(), "path": v.Path(), "error": err}).Errorf("")
return "", err
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
log.WithFields(log.Fields{"app": a.name, "now": now, "box": v.Box(), "path": v.Path()}).Debugf("%d snapshots", len(fs.snapshots))
2023-06-29 22:58:24 +02:00
for _, v2 := range fs.snapshots {
if s, err := v2.Schedule(); err == nil {
snapshots2, ok := snapshots[s]
if !ok {
snapshots2 = make(map[time.Time]int)
}
2023-06-29 22:58:24 +02:00
if t, err := v2.Timestamp(); err == nil {
if t.After(now) {
err := errors.New("snapshot in the future")
log.WithFields(log.Fields{"app": a.name, "now": now, "source": v, "timestamp": t, "error": err}).Errorf("")
return "", err
}
2023-06-29 22:58:24 +02:00
if count, ok := snapshots2[t]; ok {
snapshots2[t] = count + 1
} else {
snapshots2[t] = 1
}
}
2023-06-29 22:58:24 +02:00
snapshots[s] = snapshots2
}
}
}
2023-06-29 22:58:24 +02:00
t := time.Unix(0, 0)
if _, ok := a.schedule["yearly"]; ok {
if s, ok := snapshots["yearly"]; ok {
for k, v := range s {
if k.After(t) && v == len(a.sources) {
t = k
}
}
}
if t.Year() < now.Year() {
return "yearly", nil
}
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
if _, ok := a.schedule["monthly"]; ok {
if s, ok := snapshots["monthly"]; ok {
for k, v := range s {
if k.After(t) && v == len(a.sources) {
t = k
2021-10-16 15:39:54 +02:00
}
}
}
2023-06-29 22:58:24 +02:00
if t.Year() < now.Year() || t.Month() != now.Month() {
2023-07-01 14:10:35 +02:00
return "monthly", nil
2023-06-29 22:58:24 +02:00
}
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
if _, ok := a.schedule["weekly"]; ok {
if s, ok := snapshots["weekly"]; ok {
for k, v := range s {
if k.After(t) && v == len(a.sources) {
t = k
}
}
}
ny, nw := now.ISOWeek()
ty, tw := t.ISOWeek()
if ty < ny || tw < nw {
2023-07-01 14:10:35 +02:00
return "weekly", nil
2023-06-29 22:58:24 +02:00
}
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
if _, ok := a.schedule["daily"]; ok {
if s, ok := snapshots["daily"]; ok {
for k, v := range s {
if k.After(t) && v == len(a.sources) {
t = k
2022-06-17 16:03:12 +02:00
}
2022-04-16 15:30:47 +02:00
}
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
if t.Year() < now.Year() || t.Month() != now.Month() || t.Day() < now.Day() {
return "daily", nil
}
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
if _, ok := a.schedule["hourly"]; ok {
if s, ok := snapshots["hourly"]; ok {
for k, v := range s {
if k.After(t) && v == len(a.sources) {
t = k
}
2021-10-16 15:39:54 +02:00
}
2023-06-29 22:58:24 +02:00
}
if t.Year() < now.Year() || t.Month() != now.Month() || t.Day() < now.Day() || t.Hour() < now.Hour() {
return "hourly", nil
2021-10-16 15:39:54 +02:00
}
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
return "", nil
2021-10-18 16:13:04 +02:00
}
2023-06-29 22:58:24 +02:00
func (a *App) RunBefore(schedule string) error {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name, "schedule": schedule}).Debugf("done")
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
for k, v := range a.before {
re := regexp.MustCompile(k)
if re.MatchString(schedule) {
if _, err := v.Exec(); err != nil {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "regex": k, "call": "Exec", "error": err}).Errorf("")
return err
2021-10-18 16:13:04 +02:00
}
}
2021-11-04 14:33:44 +01:00
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
return nil
}
2022-06-17 14:54:14 +02:00
2023-06-29 22:58:24 +02:00
func (a *App) RunAfter(schedule string) error {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name, "schedule": schedule}).Debugf("done")
2021-10-19 02:45:51 +02:00
2023-06-29 22:58:24 +02:00
for k, v := range a.after {
re := regexp.MustCompile(k)
if re.MatchString(schedule) {
if _, err := v.Exec(); err != nil {
log.WithFields(log.Fields{"app": a.name, "schedule": schedule, "regex": k, "call": "Exec", "error": err}).Errorf("")
return err
2021-10-19 02:45:51 +02:00
}
}
2021-10-16 15:39:54 +02:00
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
return nil
}
2023-06-29 22:58:24 +02:00
func (a *App) Transfer() error {
log.WithFields(log.Fields{"app": a.name}).Debugf("starting")
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
for _, src := range a.sources {
for _, dest := range a.destinations {
dest := dest.Append("/" + src.Box() + "/" + src.Path())
if dest.Online() {
if err := dest.Mkdir(); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "Mkdir", "attr": dest, "error": err}).Errorf("")
return err
}
2021-10-18 16:13:04 +02:00
2023-06-29 22:58:24 +02:00
if err := TransferZfs(src, dest); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "TransferZfs", "src": src, "dest": dest, "error": err}).Errorf("")
return err
}
2021-10-16 15:39:54 +02:00
}
}
}
2021-10-18 16:13:04 +02:00
2021-10-16 15:39:54 +02:00
return nil
}