github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/deployer/deployer.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package deployer 5 6 import ( 7 "fmt" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 "github.com/juju/utils/v3" 13 "github.com/juju/worker/v3" 14 15 "github.com/juju/juju/agent" 16 "github.com/juju/juju/core/life" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/core/watcher" 19 "github.com/juju/juju/rpc/params" 20 ) 21 22 // logger is here to stop the desire of creating a package level logger. 23 // Don't do this, instead pass one through as config to the worker. 24 type logger interface{} 25 26 var _ logger = struct{}{} 27 28 // Deployer is responsible for deploying and recalling unit agents, according 29 // to changes in a set of state units; and for the final removal of its agents' 30 // units from state when they are no longer needed. 31 type Deployer struct { 32 st API 33 logger Logger 34 ctx Context 35 deployed set.Strings 36 } 37 38 // API is used to define the methods that the deployer makes. 39 type API interface { 40 Machine(names.MachineTag) (Machine, error) 41 Unit(names.UnitTag) (Unit, error) 42 } 43 44 // Machine defines the methods that the deployer makes on a machine in 45 // the model. 46 type Machine interface { 47 WatchUnits() (watcher.StringsWatcher, error) 48 } 49 50 // Unit defines the methods that the deployer makes on a unit in the model. 51 type Unit interface { 52 Life() life.Value 53 Name() string 54 Remove() error 55 SetPassword(password string) error 56 SetStatus(unitStatus status.Status, info string, data map[string]interface{}) error 57 } 58 59 // Context abstracts away the differences between different unit deployment 60 // strategies; where a Deployer is responsible for what to deploy, a Context 61 // is responsible for how to deploy. 62 type Context interface { 63 worker.Worker 64 65 // DeployUnit causes the agent for the specified unit to be started and run 66 // continuously until further notice without further intervention. It will 67 // return an error if the agent is already deployed. 68 DeployUnit(unitName, initialPassword string) error 69 70 // RecallUnit causes the agent for the specified unit to be stopped, and 71 // the agent's data to be destroyed. It will return an error if the agent 72 // was not deployed by the manager. 73 RecallUnit(unitName string) error 74 75 // DeployedUnits returns the names of all units deployed by the manager. 76 DeployedUnits() ([]string, error) 77 78 // AgentConfig returns the agent config for the machine agent that is 79 // running the deployer. 80 AgentConfig() agent.Config 81 82 Report() map[string]interface{} 83 } 84 85 // NewDeployer returns a Worker that deploys and recalls unit agents 86 // via ctx, taking a machine id to operate on. 87 func NewDeployer(st API, logger Logger, ctx Context) (worker.Worker, error) { 88 d := &Deployer{ 89 st: st, 90 logger: logger, 91 ctx: ctx, 92 deployed: make(set.Strings), 93 } 94 w, err := watcher.NewStringsWorker(watcher.StringsConfig{ 95 Handler: d, 96 }) 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 return w, nil 101 } 102 103 // Report is shown in the engine report. 104 func (d *Deployer) Report() map[string]interface{} { 105 // Get the report from the context. 106 return d.ctx.Report() 107 } 108 109 // SetUp is called by the NewStringsWorker to create the watcher that drives the 110 // worker. 111 func (d *Deployer) SetUp() (watcher.StringsWatcher, error) { 112 d.logger.Tracef("SetUp") 113 tag := d.ctx.AgentConfig().Tag() 114 machineTag, ok := tag.(names.MachineTag) 115 if !ok { 116 return nil, errors.Errorf("expected names.MachineTag, got %T", tag) 117 } 118 d.logger.Tracef("getting Machine %s", machineTag) 119 machine, err := d.st.Machine(machineTag) 120 if err != nil { 121 return nil, err 122 } 123 d.logger.Tracef("getting units watcher") 124 machineUnitsWatcher, err := machine.WatchUnits() 125 if err != nil { 126 d.logger.Tracef("error: %v", err) 127 return nil, err 128 } 129 d.logger.Tracef("looking for deployed units") 130 131 deployed, err := d.ctx.DeployedUnits() 132 if err != nil { 133 return nil, err 134 } 135 d.logger.Tracef("deployed units: %v", deployed) 136 for _, unitName := range deployed { 137 d.deployed.Add(unitName) 138 if err := d.changed(unitName); err != nil { 139 return nil, err 140 } 141 } 142 return machineUnitsWatcher, nil 143 } 144 145 // Handle is called for new value in the StringsWatcher. 146 func (d *Deployer) Handle(_ <-chan struct{}, unitNames []string) error { 147 d.logger.Tracef("Handle: %v", unitNames) 148 for _, unitName := range unitNames { 149 if err := d.changed(unitName); err != nil { 150 return err 151 } 152 } 153 return nil 154 } 155 156 // changed ensures that the named unit is deployed, recalled, or removed, as 157 // indicated by its state. 158 func (d *Deployer) changed(unitName string) error { 159 unitTag := names.NewUnitTag(unitName) 160 // Determine unit life state, and whether we're responsible for it. 161 d.logger.Infof("checking unit %q", unitName) 162 var unitLife life.Value 163 unit, err := d.st.Unit(unitTag) 164 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 165 unitLife = life.Dead 166 } else if err != nil { 167 return err 168 } else { 169 unitLife = unit.Life() 170 } 171 // Deployed units must be removed if they're Dead, or if the deployer 172 // is no longer responsible for them. 173 if d.deployed.Contains(unitName) { 174 if unitLife == life.Dead { 175 if err := d.recall(unitName); err != nil { 176 return err 177 } 178 } 179 } 180 // The only units that should be deployed are those that (1) we are responsible 181 // for and (2) are Alive -- if we're responsible for a Dying unit that is not 182 // yet deployed, we should remove it immediately rather than undergo the hassle 183 // of deploying a unit agent purely so it can set itself to Dead. 184 if !d.deployed.Contains(unitName) { 185 if unitLife == life.Alive { 186 return d.deploy(unit) 187 } else if unit != nil { 188 return d.remove(unit) 189 } 190 } 191 return nil 192 } 193 194 // deploy will deploy the supplied unit with the deployer's manager. It will 195 // panic if it observes inconsistent internal state. 196 func (d *Deployer) deploy(unit Unit) error { 197 unitName := unit.Name() 198 if d.deployed.Contains(unit.Name()) { 199 panic("must not re-deploy a deployed unit") 200 } 201 if err := unit.SetStatus(status.Waiting, status.MessageInstallingAgent, nil); err != nil { 202 return errors.Trace(err) 203 } 204 d.logger.Infof("deploying unit %q", unitName) 205 initialPassword, err := utils.RandomPassword() 206 if err != nil { 207 return err 208 } 209 if err := unit.SetPassword(initialPassword); err != nil { 210 return fmt.Errorf("cannot set password for unit %q: %v", unitName, err) 211 } 212 if err := d.ctx.DeployUnit(unitName, initialPassword); err != nil { 213 return err 214 } 215 d.deployed.Add(unitName) 216 return nil 217 } 218 219 // recall will recall the named unit with the deployer's manager. It will 220 // panic if it observes inconsistent internal state. 221 func (d *Deployer) recall(unitName string) error { 222 if !d.deployed.Contains(unitName) { 223 panic("must not recall a unit that is not deployed") 224 } 225 d.logger.Infof("recalling unit %q", unitName) 226 if err := d.ctx.RecallUnit(unitName); err != nil { 227 return err 228 } 229 d.deployed.Remove(unitName) 230 return nil 231 } 232 233 // remove will remove the supplied unit from state. It will panic if it 234 // observes inconsistent internal state. 235 func (d *Deployer) remove(unit Unit) error { 236 unitName := unit.Name() 237 if d.deployed.Contains(unitName) { 238 panic("must not remove a deployed unit") 239 } else if unit.Life() == life.Alive { 240 panic("must not remove an Alive unit") 241 } 242 d.logger.Infof("removing unit %q", unitName) 243 return unit.Remove() 244 } 245 246 // TearDown stops the embedded context. 247 func (d *Deployer) TearDown() error { 248 d.ctx.Kill() 249 return d.ctx.Wait() 250 }