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 }