github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/charmrevision/worker.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package charmrevision
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/retry"
    12  	"github.com/juju/worker/v3"
    13  	"gopkg.in/tomb.v2"
    14  )
    15  
    16  // logger is here to stop the desire of creating a package level logger.
    17  // Don't do this, instead pass one in.
    18  type logger interface{}
    19  
    20  var _ logger = struct{}{}
    21  
    22  // RevisionUpdater exposes the "single" capability required by the worker.
    23  // As the worker gains more responsibilities, it will likely need more; see
    24  // storageprovisioner for a helpful model to grow towards.
    25  type RevisionUpdater interface {
    26  
    27  	// UpdateLatestRevisions causes the environment to be scanned, the charm
    28  	// store to be interrogated, and model representations of updated charms
    29  	// to be stored in the environment.
    30  	//
    31  	// That is sufficiently complex that the logic should be implemented by
    32  	// the worker, not directly on the apiserver; as this functionality needs
    33  	// to change/mature, please migrate responsibilities down to the worker
    34  	// and grow this interface to match.
    35  	UpdateLatestRevisions() error
    36  }
    37  
    38  // Config defines the operation of a charm revision updater worker.
    39  type Config struct {
    40  
    41  	// RevisionUpdater is the worker's view of the controller.
    42  	RevisionUpdater RevisionUpdater
    43  
    44  	// Clock is the worker's view of time.
    45  	Clock clock.Clock
    46  
    47  	// Period is the time between charm revision updates.
    48  	Period time.Duration
    49  
    50  	// Logger is the logger used for debug logging in this worker.
    51  	Logger Logger
    52  }
    53  
    54  // Logger is a debug-only logger interface.
    55  type Logger interface {
    56  	Debugf(message string, args ...interface{})
    57  }
    58  
    59  // Validate returns an error if the configuration cannot be expected
    60  // to start a functional worker.
    61  func (config Config) Validate() error {
    62  	if config.RevisionUpdater == nil {
    63  		return errors.NotValidf("nil RevisionUpdater")
    64  	}
    65  	if config.Clock == nil {
    66  		return errors.NotValidf("nil Clock")
    67  	}
    68  	if config.Period <= 0 {
    69  		return errors.NotValidf("non-positive Period")
    70  	}
    71  	if config.Logger == nil {
    72  		return errors.NotValidf("nil Logger")
    73  	}
    74  	return nil
    75  }
    76  
    77  // NewWorker returns a worker that calls UpdateLatestRevisions on the
    78  // configured RevisionUpdater, once when started and subsequently every
    79  // Period.
    80  func NewWorker(config Config) (worker.Worker, error) {
    81  	if err := config.Validate(); err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	w := &revisionUpdateWorker{
    85  		config: config,
    86  	}
    87  	w.config.Logger.Debugf("worker created with period %v", w.config.Period)
    88  	w.tomb.Go(w.loop)
    89  	return w, nil
    90  }
    91  
    92  type revisionUpdateWorker struct {
    93  	tomb   tomb.Tomb
    94  	config Config
    95  }
    96  
    97  func (ruw *revisionUpdateWorker) loop() error {
    98  	for {
    99  		select {
   100  		case <-ruw.tomb.Dying():
   101  			return tomb.ErrDying
   102  
   103  		// TODO (stickupkid): Instead of applying a large jitter, we should
   104  		// instead attempt to claim a lease to for the required period. Release
   105  		// the lease on the termination of the worker. Other HA nodes can
   106  		// update then claim the lease and run the checks.
   107  		case <-ruw.config.Clock.After(jitter(ruw.config.Period)):
   108  			ruw.config.Logger.Debugf("%v elapsed, performing work", ruw.config.Period)
   109  			err := ruw.config.RevisionUpdater.UpdateLatestRevisions()
   110  			if err != nil {
   111  				return errors.Trace(err)
   112  			}
   113  		}
   114  	}
   115  }
   116  
   117  func jitter(period time.Duration) time.Duration {
   118  	return retry.ExpBackoff(period, period*2, 2, true)(0, 1)
   119  }
   120  
   121  // Kill is part of the worker.Worker interface.
   122  func (ruw *revisionUpdateWorker) Kill() {
   123  	ruw.tomb.Kill(nil)
   124  }
   125  
   126  // Wait is part of the worker.Worker interface.
   127  func (ruw *revisionUpdateWorker) Wait() error {
   128  	return ruw.tomb.Wait()
   129  }