github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/instancemutater/worker.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package instancemutater 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/catacomb" 13 "github.com/juju/worker/v3/dependency" 14 15 "github.com/juju/juju/agent" 16 "github.com/juju/juju/api/agent/instancemutater" 17 "github.com/juju/juju/core/watcher" 18 "github.com/juju/juju/environs" 19 ) 20 21 type InstanceMutaterAPI interface { 22 WatchModelMachines() (watcher.StringsWatcher, error) 23 Machine(tag names.MachineTag) (instancemutater.MutaterMachine, error) 24 } 25 26 // Logger represents the logging methods called. 27 type Logger interface { 28 Warningf(message string, args ...interface{}) 29 Infof(message string, args ...interface{}) 30 Debugf(message string, args ...interface{}) 31 Errorf(message string, args ...interface{}) 32 Tracef(message string, args ...interface{}) 33 } 34 35 // Config represents the configuration required to run a new instance machineApi 36 // worker. 37 type Config struct { 38 Facade InstanceMutaterAPI 39 40 // Logger is the Logger for this worker. 41 Logger Logger 42 43 Broker environs.LXDProfiler 44 45 AgentConfig agent.Config 46 47 // Tag is the current MutaterMachine tag 48 Tag names.Tag 49 50 // GetMachineWatcher allows the worker to watch different "machines" 51 // depending on whether this work is running with an environ broker 52 // or a container broker. 53 GetMachineWatcher func() (watcher.StringsWatcher, error) 54 55 // GetRequiredLXDProfiles provides a slice of strings representing the 56 // lxd profiles to be included on every LXD machine used given the 57 // current model name. 58 GetRequiredLXDProfiles RequiredLXDProfilesFunc 59 60 // GetRequiredContext provides a way to override the given context 61 // Note: the following is required for testing purposes when we have an 62 // error case and we want to know when it's valid to kill/clean the worker. 63 GetRequiredContext RequiredMutaterContextFunc 64 } 65 66 type RequiredLXDProfilesFunc func(string) []string 67 68 type RequiredMutaterContextFunc func(MutaterContext) MutaterContext 69 70 // Validate checks for missing values from the configuration and checks that 71 // they conform to a given type. 72 func (config Config) Validate() error { 73 if config.Logger == nil { 74 return errors.NotValidf("nil Logger") 75 } 76 if config.Facade == nil { 77 return errors.NotValidf("nil Facade") 78 } 79 if config.Broker == nil { 80 return errors.NotValidf("nil Broker") 81 } 82 if config.AgentConfig == nil { 83 return errors.NotValidf("nil AgentConfig") 84 } 85 if config.Tag == nil { 86 return errors.NotValidf("nil Tag") 87 } 88 if _, ok := config.Tag.(names.MachineTag); !ok { 89 if config.Tag.Kind() != names.ControllerAgentTagKind { 90 // On K8s controllers, the controller agent has a ControllerAgentTagKind not a MachineKind 91 // However, we shouldn't be running the InstanceMutater worker to track the state on the Controller 92 // machine anyway. This is a hack for bug #1866623 93 return errors.NotValidf("Tag of kind %v", config.Tag.Kind()) 94 } 95 config.Logger.Debugf("asked to start an instance mutator with Tag of kind %q", config.Tag.Kind()) 96 } 97 if config.GetMachineWatcher == nil { 98 return errors.NotValidf("nil GetMachineWatcher") 99 } 100 if config.GetRequiredLXDProfiles == nil { 101 return errors.NotValidf("nil GetRequiredLXDProfiles") 102 } 103 if config.GetRequiredContext == nil { 104 return errors.NotValidf("nil GetRequiredContext") 105 } 106 return nil 107 } 108 109 // NewEnvironWorker returns a worker that keeps track of 110 // the machines in the state and polls their instance 111 // for addition or removal changes. 112 func NewEnvironWorker(config Config) (worker.Worker, error) { 113 config.GetMachineWatcher = config.Facade.WatchModelMachines 114 config.GetRequiredLXDProfiles = func(modelName string) []string { 115 return []string{"default", "juju-" + modelName} 116 } 117 config.GetRequiredContext = func(ctx MutaterContext) MutaterContext { 118 return ctx 119 } 120 return newWorker(config) 121 } 122 123 // NewContainerWorker returns a worker that keeps track of 124 // the containers in the state for this machine agent and 125 // polls their instance for addition or removal changes. 126 func NewContainerWorker(config Config) (worker.Worker, error) { 127 if _, ok := config.Tag.(names.MachineTag); !ok { 128 config.Logger.Warningf("cannot start a ContainerWorker on a %q, not restarting", config.Tag.Kind()) 129 return nil, dependency.ErrUninstall 130 } 131 m, err := config.Facade.Machine(config.Tag.(names.MachineTag)) 132 if err != nil { 133 return nil, errors.Trace(err) 134 } 135 config.GetRequiredLXDProfiles = func(_ string) []string { return []string{"default"} } 136 config.GetMachineWatcher = m.WatchContainers 137 config.GetRequiredContext = func(ctx MutaterContext) MutaterContext { 138 return ctx 139 } 140 return newWorker(config) 141 } 142 143 func newWorker(config Config) (*mutaterWorker, error) { 144 if err := config.Validate(); err != nil { 145 return nil, errors.Trace(err) 146 } 147 watcher, err := config.GetMachineWatcher() 148 if err != nil { 149 return nil, errors.Trace(err) 150 } 151 w := &mutaterWorker{ 152 logger: config.Logger, 153 facade: config.Facade, 154 broker: config.Broker, 155 machineWatcher: watcher, 156 getRequiredLXDProfilesFunc: config.GetRequiredLXDProfiles, 157 getRequiredContextFunc: config.GetRequiredContext, 158 } 159 // getRequiredContextFunc returns a MutaterContext, this is for overriding 160 // during testing. 161 err = catacomb.Invoke(catacomb.Plan{ 162 Site: &w.catacomb, 163 Work: w.loop, 164 Init: []worker.Worker{watcher}, 165 }) 166 if err != nil { 167 return nil, errors.Trace(err) 168 } 169 return w, nil 170 } 171 172 type mutaterWorker struct { 173 catacomb catacomb.Catacomb 174 175 logger Logger 176 broker environs.LXDProfiler 177 facade InstanceMutaterAPI 178 machineWatcher watcher.StringsWatcher 179 getRequiredLXDProfilesFunc RequiredLXDProfilesFunc 180 getRequiredContextFunc RequiredMutaterContextFunc 181 } 182 183 func (w *mutaterWorker) loop() error { 184 var wg sync.WaitGroup 185 defer wg.Wait() 186 m := &mutater{ 187 context: w.getRequiredContextFunc(w), 188 logger: w.logger, 189 wg: &wg, 190 machines: make(map[names.MachineTag]chan struct{}), 191 machineDead: make(chan instancemutater.MutaterMachine), 192 } 193 for { 194 select { 195 case <-m.context.dying(): 196 return m.context.errDying() 197 case ids, ok := <-w.machineWatcher.Changes(): 198 if !ok { 199 return errors.New("machines watcher closed") 200 } 201 tags := make([]names.MachineTag, len(ids)) 202 for i := range ids { 203 tags[i] = names.NewMachineTag(ids[i]) 204 } 205 if err := m.startMachines(tags); err != nil { 206 return err 207 } 208 case d := <-m.machineDead: 209 delete(m.machines, d.Tag()) 210 } 211 } 212 } 213 214 // Kill implements worker.Worker.Kill. 215 func (w *mutaterWorker) Kill() { 216 w.catacomb.Kill(nil) 217 } 218 219 // Wait implements worker.Worker.Wait. 220 func (w *mutaterWorker) Wait() error { 221 return w.catacomb.Wait() 222 } 223 224 // Stop stops the mutaterWorker and returns any 225 // error it encountered when running. 226 func (w *mutaterWorker) Stop() error { 227 w.Kill() 228 return w.Wait() 229 } 230 231 // newMachineContext is part of the mutaterContext interface. 232 func (w *mutaterWorker) newMachineContext() MachineContext { 233 return w.getRequiredContextFunc(w) 234 } 235 236 // getMachine is part of the MachineContext interface. 237 func (w *mutaterWorker) getMachine(tag names.MachineTag) (instancemutater.MutaterMachine, error) { 238 m, err := w.facade.Machine(tag) 239 return m, err 240 } 241 242 // getBroker is part of the MachineContext interface. 243 func (w *mutaterWorker) getBroker() environs.LXDProfiler { 244 return w.broker 245 } 246 247 // getRequiredLXDProfiles part of the MachineContext interface. 248 func (w *mutaterWorker) getRequiredLXDProfiles(modelName string) []string { 249 return w.getRequiredLXDProfilesFunc(modelName) 250 } 251 252 // KillWithError is part of the lifetimeContext interface. 253 func (w *mutaterWorker) KillWithError(err error) { 254 w.catacomb.Kill(err) 255 } 256 257 // dying is part of the lifetimeContext interface. 258 func (w *mutaterWorker) dying() <-chan struct{} { 259 return w.catacomb.Dying() 260 } 261 262 // errDying is part of the lifetimeContext interface. 263 func (w *mutaterWorker) errDying() error { 264 return w.catacomb.ErrDying() 265 } 266 267 // add is part of the lifetimeContext interface. 268 func (w *mutaterWorker) add(new worker.Worker) error { 269 return w.catacomb.Add(new) 270 }