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  }