github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/dblogpruner/worker.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package dblogpruner 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "gopkg.in/juju/worker.v1" 14 "gopkg.in/tomb.v2" 15 16 "github.com/juju/juju/state" 17 ) 18 19 var logger = loggo.GetLogger("juju.worker.dblogpruner") 20 21 type Config struct { 22 State *state.State 23 Clock clock.Clock 24 PruneInterval time.Duration 25 } 26 27 func (config Config) Validate() error { 28 if config.State == nil { 29 return errors.NotValidf("nil State") 30 } 31 if config.Clock == nil { 32 return errors.NotValidf("nil Clock") 33 } 34 if config.PruneInterval <= 0 { 35 return errors.NotValidf("non-positive PruneInterval") 36 } 37 return nil 38 } 39 40 // NewWorker returns a worker which periodically wakes up to remove old log 41 // entries stored in MongoDB. This worker must not be run in more than one 42 // agent concurrently. 43 func NewWorker(config Config) (worker.Worker, error) { 44 if err := config.Validate(); err != nil { 45 return nil, errors.Trace(err) 46 } 47 w := &pruneWorker{ 48 config: config, 49 } 50 w.tomb.Go(w.loop) 51 return w, nil 52 } 53 54 type pruneWorker struct { 55 tomb tomb.Tomb 56 mu sync.Mutex 57 config Config 58 current report 59 } 60 61 func (w *pruneWorker) Report() map[string]interface{} { 62 w.mu.Lock() 63 report := w.current 64 w.mu.Unlock() 65 66 // The keys used give a nice output when alphabetical 67 // which is how the report yaml gets serialised. 68 result := map[string]interface{}{ 69 "prune-age": report.maxLogAge, 70 "prune-size": report.maxCollectionMB, 71 } 72 if !report.lastPrune.IsZero() { 73 result["last-prune"] = report.lastPrune.Round(time.Second) 74 } 75 if !report.nextPrune.IsZero() { 76 result["next-prune"] = report.nextPrune.Round(time.Second) 77 } 78 if report.message != "" { 79 result["summary"] = report.message 80 } 81 if report.pruning { 82 result["pruning-in-progress"] = true 83 } 84 return result 85 } 86 87 type reportRequest struct { 88 response chan<- report 89 } 90 91 type report struct { 92 lastPrune time.Time 93 nextPrune time.Time 94 maxLogAge time.Duration 95 maxCollectionMB int 96 message string 97 pruning bool 98 } 99 100 func (w *pruneWorker) loop() error { 101 controllerConfigWatcher := w.config.State.WatchControllerConfig() 102 defer worker.Stop(controllerConfigWatcher) 103 104 var prune <-chan time.Time 105 for { 106 select { 107 case <-w.tomb.Dying(): 108 return tomb.ErrDying 109 110 case _, ok := <-controllerConfigWatcher.Changes(): 111 if !ok { 112 return errors.New("controller configuration watcher closed") 113 } 114 controllerConfig, err := w.config.State.ControllerConfig() 115 if err != nil { 116 return errors.Annotate(err, "cannot load controller configuration") 117 } 118 newMaxAge := controllerConfig.MaxLogsAge() 119 newMaxCollectionMB := controllerConfig.MaxLogSizeMB() 120 if newMaxAge != w.current.maxLogAge || newMaxCollectionMB != w.current.maxCollectionMB { 121 w.mu.Lock() 122 w.current.maxLogAge = newMaxAge 123 w.current.maxCollectionMB = newMaxCollectionMB 124 w.mu.Unlock() 125 logger.Infof("log pruning config: max age: %v, max collection size %dM", newMaxAge, newMaxCollectionMB) 126 } 127 if prune == nil { 128 // We defer starting the timer until the 129 // controller configuration watcher fires 130 // for the first time, and we have correct 131 // configuration values for pruning below. 132 prune = w.config.Clock.After(w.config.PruneInterval) 133 w.mu.Lock() 134 w.current.nextPrune = w.config.Clock.Now().Add(w.config.PruneInterval) 135 w.mu.Unlock() 136 } 137 138 case <-prune: 139 now := w.config.Clock.Now() 140 prune = w.config.Clock.After(w.config.PruneInterval) 141 w.mu.Lock() 142 w.current.lastPrune = now 143 w.current.nextPrune = now.Add(w.config.PruneInterval) 144 w.current.pruning = true 145 w.mu.Unlock() 146 147 minLogTime := now.Add(-w.current.maxLogAge) 148 message, err := state.PruneLogs(w.config.State, minLogTime, w.current.maxCollectionMB, logger) 149 if err != nil { 150 return errors.Trace(err) 151 } 152 w.mu.Lock() 153 w.current.pruning = false 154 w.current.message = message 155 w.mu.Unlock() 156 } 157 } 158 } 159 160 // Kill implements Worker.Kill(). 161 func (w *pruneWorker) Kill() { 162 w.tomb.Kill(nil) 163 } 164 165 // Wait implements Worker.Wait(). 166 func (w *pruneWorker) Wait() error { 167 return w.tomb.Wait() 168 }