package admin
import (
"bytes"
"context"
"errors"
"fmt"
"math/rand"
"sync"
"time"
"github.com/google/trillian/monitoring"
"github.com/google/trillian/storage"
"k8s.io/klog/v2"
)
const (
deleteErrReason = "delete_error"
timestampParseErrReson = "timestamp_parse_error"
)
var (
timeNow = time.Now
timeAfter = time.After
hardDeleteCounter monitoring.Counter
metricsOnce sync.Once
)
func incHardDeleteCounter(treeID int64, success bool, reason string) {
hardDeleteCounter.Inc(fmt.Sprint(treeID), fmt.Sprint(success), reason)
}
type DeletedTreeGC struct {
admin storage.AdminStorage
deleteThreshold time.Duration
minRunInterval time.Duration
}
func NewDeletedTreeGC(admin storage.AdminStorage, threshold, minRunInterval time.Duration, mf monitoring.MetricFactory) *DeletedTreeGC {
gc := &DeletedTreeGC{
admin: admin,
deleteThreshold: threshold,
minRunInterval: minRunInterval,
}
metricsOnce.Do(func() {
if mf == nil {
mf = monitoring.InertMetricFactory{}
}
hardDeleteCounter = mf.NewCounter("tree_hard_delete_counter", "Counter of hard-deleted trees", monitoring.TreeIDLabel, "success", "reason")
})
return gc
}
func (gc *DeletedTreeGC) Run(ctx context.Context) {
for {
count, err := gc.RunOnce(ctx)
if err != nil {
klog.Errorf("DeletedTreeGC.Run: %v", err)
}
if count > 0 {
klog.Infof("DeletedTreeGC.Run: successfully deleted %v trees", count)
}
d := gc.minRunInterval + time.Duration(rand.Int63n(gc.minRunInterval.Nanoseconds()))
select {
case <-ctx.Done():
return
case <-timeAfter(d):
}
}
}
func (gc *DeletedTreeGC) RunOnce(ctx context.Context) (int, error) {
now := timeNow()
trees, err := storage.ListTrees(ctx, gc.admin, true )
if err != nil {
return 0, fmt.Errorf("error listing trees: %v", err)
}
count := 0
var errs []error
for _, tree := range trees {
if !tree.Deleted {
continue
}
if err := tree.DeleteTime.CheckValid(); err != nil {
errs = append(errs, fmt.Errorf("error parsing delete_time of tree %v: %v", tree.TreeId, err))
incHardDeleteCounter(tree.TreeId, false, timestampParseErrReson)
continue
}
deleteTime := tree.DeleteTime.AsTime()
durationSinceDelete := now.Sub(deleteTime)
if durationSinceDelete <= gc.deleteThreshold {
continue
}
klog.Infof("DeletedTreeGC.RunOnce: Hard-deleting tree %v after %v", tree.TreeId, durationSinceDelete)
if err := storage.HardDeleteTree(ctx, gc.admin, tree.TreeId); err != nil {
errs = append(errs, fmt.Errorf("error hard-deleting tree %v: %v", tree.TreeId, err))
incHardDeleteCounter(tree.TreeId, false, deleteErrReason)
continue
}
count++
incHardDeleteCounter(tree.TreeId, true, "")
}
if len(errs) == 0 {
return count, nil
}
buf := &bytes.Buffer{}
buf.WriteString("encountered errors hard-deleting trees:")
for _, err := range errs {
buf.WriteString("\n\t")
buf.WriteString(err.Error())
}
return count, errors.New(buf.String())
}