github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasupgrader/upgrader.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasupgrader 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/names/v5" 12 "github.com/juju/version/v2" 13 "github.com/juju/worker/v3/catacomb" 14 15 "github.com/juju/juju/api/agent/agent" 16 "github.com/juju/juju/core/arch" 17 coreos "github.com/juju/juju/core/os" 18 "github.com/juju/juju/core/watcher" 19 jujuversion "github.com/juju/juju/version" 20 "github.com/juju/juju/worker/gate" 21 "github.com/juju/juju/worker/upgrader" 22 ) 23 24 var logger = loggo.GetLogger("juju.worker.caasupgrader") 25 26 // Upgrader represents a worker that watches the state for upgrade 27 // requests for a given CAAS agent. 28 type Upgrader struct { 29 catacomb catacomb.Catacomb 30 31 upgraderClient UpgraderClient 32 operatorUpgrader CAASOperatorUpgrader 33 tag names.Tag 34 config Config 35 } 36 37 // UpgraderClient provides the facade methods used by the worker. 38 type UpgraderClient interface { 39 DesiredVersion(tag string) (version.Number, error) 40 SetVersion(tag string, v version.Binary) error 41 WatchAPIVersion(agentTag string) (watcher.NotifyWatcher, error) 42 } 43 44 type CAASOperatorUpgrader interface { 45 Upgrade(agentTag string, v version.Number) error 46 } 47 48 // Config contains the items the worker needs to start. 49 type Config struct { 50 UpgraderClient UpgraderClient 51 CAASOperatorUpgrader CAASOperatorUpgrader 52 AgentTag names.Tag 53 OrigAgentVersion version.Number 54 UpgradeStepsWaiter gate.Waiter 55 InitialUpgradeCheckComplete gate.Unlocker 56 } 57 58 // NewUpgrader returns a new upgrader worker. It watches changes to the 59 // current version of a CAAS agent. If an upgrade is needed, the worker 60 // updates the docker image version for the specified agent. 61 // TODO(caas) - support HA controllers 62 func NewUpgrader(config Config) (*Upgrader, error) { 63 u := &Upgrader{ 64 upgraderClient: config.UpgraderClient, 65 operatorUpgrader: config.CAASOperatorUpgrader, 66 tag: config.AgentTag, 67 config: config, 68 } 69 err := catacomb.Invoke(catacomb.Plan{ 70 Site: &u.catacomb, 71 Work: u.loop, 72 }) 73 if err != nil { 74 return nil, errors.Trace(err) 75 } 76 return u, nil 77 } 78 79 // Kill implements worker.Worker.Kill. 80 func (u *Upgrader) Kill() { 81 u.catacomb.Kill(nil) 82 } 83 84 // Wait implements worker.Worker.Wait. 85 func (u *Upgrader) Wait() error { 86 return u.catacomb.Wait() 87 } 88 89 func (u *Upgrader) loop() error { 90 // Only controllers and sidecar unit agents set their version here - application agents do it in the main agent worker loop. 91 hostOSType := coreos.HostOSTypeName() 92 if agent.IsAllowedControllerTag(u.tag.Kind()) || u.tag.Kind() == names.UnitTagKind { 93 if err := u.upgraderClient.SetVersion(u.tag.String(), toBinaryVersion(jujuversion.Current, hostOSType)); err != nil { 94 return errors.Annotatef(err, "cannot set agent version for %q", u.tag.String()) 95 } 96 } 97 98 // We don't read on the dying channel until we have received the 99 // initial event from the API version watcher, thus ensuring 100 // that we attempt an upgrade even if other workers are dying 101 // all around us. Similarly, we don't want to bind the watcher 102 // to the catacomb's lifetime (yet!) lest we wait forever for a 103 // stopped watcher. 104 // 105 // However, that absolutely depends on versionWatcher's guaranteed 106 // initial event, and we should assume that it'll break its contract 107 // sometime. So we allow the watcher to wait patiently for the event 108 // for a full minute; but after that we proceed regardless. 109 versionWatcher, err := u.upgraderClient.WatchAPIVersion(u.tag.String()) 110 if err != nil { 111 return errors.Annotate(err, "getting upgrader facade watch api version client") 112 } 113 logger.Infof("abort check blocked until version event received") 114 // TODO(fwereade): 2016-03-17 lp:1558657 115 mustProceed := time.After(time.Minute) 116 var dying <-chan struct{} 117 allowDying := func() { 118 if dying == nil { 119 logger.Infof("unblocking abort check") 120 mustProceed = nil 121 dying = u.catacomb.Dying() 122 if err := u.catacomb.Add(versionWatcher); err != nil { 123 u.catacomb.Kill(err) 124 } 125 } 126 } 127 128 logger.Debugf("current agent binary version: %v", jujuversion.Current) 129 for { 130 select { 131 // NOTE: dying starts out nil, so it can't be chosen 132 // first time round the loop. However... 133 case <-dying: 134 return u.catacomb.ErrDying() 135 // ...*every* other case *must* allowDying(), before doing anything 136 // else, lest an error cause us to leak versionWatcher. 137 case <-mustProceed: 138 logger.Infof("version event not received after one minute") 139 allowDying() 140 case _, ok := <-versionWatcher.Changes(): 141 allowDying() 142 if !ok { 143 return errors.New("version watcher closed") 144 } 145 } 146 147 wantVersion, err := u.upgraderClient.DesiredVersion(u.tag.String()) 148 if err != nil { 149 return err 150 } 151 152 if wantVersion == jujuversion.Current { 153 u.config.InitialUpgradeCheckComplete.Unlock() 154 continue 155 } else if !upgrader.AllowedTargetVersion(jujuversion.Current, wantVersion) { 156 logger.Warningf("desired agent binary version: %s is older than current %s, refusing to downgrade", 157 wantVersion, jujuversion.Current) 158 u.config.InitialUpgradeCheckComplete.Unlock() 159 continue 160 } 161 direction := "upgrade" 162 if wantVersion.Compare(jujuversion.Current) == -1 { 163 direction = "downgrade" 164 } 165 logger.Debugf("%s requested for %v from %v to %v", direction, u.tag, jujuversion.Current, wantVersion) 166 err = u.operatorUpgrader.Upgrade(u.tag.String(), wantVersion) 167 if err != nil { 168 return errors.Annotatef( 169 err, "requesting upgrade for %v from %v to %v", u.tag.String(), jujuversion.Current, wantVersion) 170 } 171 } 172 } 173 174 func toBinaryVersion(vers version.Number, osType string) version.Binary { 175 outVers := version.Binary{ 176 Number: vers, 177 Arch: arch.HostArch(), 178 Release: osType, 179 } 180 return outVers 181 }