github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/provisioner/provisioner.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/loggo" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/agent" 15 apiprovisioner "github.com/juju/juju/api/provisioner" 16 "github.com/juju/juju/controller/authentication" 17 "github.com/juju/juju/environs" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/instance" 20 "github.com/juju/juju/watcher" 21 "github.com/juju/juju/worker" 22 "github.com/juju/juju/worker/catacomb" 23 ) 24 25 var logger = loggo.GetLogger("juju.provisioner") 26 27 // Ensure our structs implement the required Provisioner interface. 28 var _ Provisioner = (*environProvisioner)(nil) 29 var _ Provisioner = (*containerProvisioner)(nil) 30 31 var ( 32 retryStrategyDelay = 10 * time.Second 33 retryStrategyCount = 3 34 ) 35 36 // Provisioner represents a running provisioner worker. 37 type Provisioner interface { 38 worker.Worker 39 getMachineWatcher() (watcher.StringsWatcher, error) 40 getRetryWatcher() (watcher.NotifyWatcher, error) 41 } 42 43 // environProvisioner represents a running provisioning worker for machine nodes 44 // belonging to an environment. 45 type environProvisioner struct { 46 provisioner 47 environ environs.Environ 48 configObserver 49 } 50 51 // containerProvisioner represents a running provisioning worker for containers 52 // hosted on a machine. 53 type containerProvisioner struct { 54 provisioner 55 containerType instance.ContainerType 56 machine *apiprovisioner.Machine 57 configObserver 58 } 59 60 // provisioner providers common behaviour for a running provisioning worker. 61 type provisioner struct { 62 Provisioner 63 st *apiprovisioner.State 64 agentConfig agent.Config 65 broker environs.InstanceBroker 66 toolsFinder ToolsFinder 67 catacomb catacomb.Catacomb 68 } 69 70 // RetryStrategy defines the retry behavior when encountering a retryable 71 // error during provisioning. 72 // 73 // TODO(katco): 2016-08-09: lp:1611427 74 type RetryStrategy struct { 75 retryDelay time.Duration 76 retryCount int 77 } 78 79 // NewRetryStrategy returns a new retry strategy with the specified delay and 80 // count for use with retryable provisioning errors. 81 func NewRetryStrategy(delay time.Duration, count int) RetryStrategy { 82 return RetryStrategy{ 83 retryDelay: delay, 84 retryCount: count, 85 } 86 } 87 88 // configObserver is implemented so that tests can see 89 // when the environment configuration changes. 90 type configObserver struct { 91 sync.Mutex 92 observer chan<- *config.Config 93 } 94 95 // notify notifies the observer of a configuration change. 96 func (o *configObserver) notify(cfg *config.Config) { 97 o.Lock() 98 if o.observer != nil { 99 o.observer <- cfg 100 } 101 o.Unlock() 102 } 103 104 // Kill implements worker.Worker.Kill. 105 func (p *provisioner) Kill() { 106 p.catacomb.Kill(nil) 107 } 108 109 // Wait implements worker.Worker.Wait. 110 func (p *provisioner) Wait() error { 111 return p.catacomb.Wait() 112 } 113 114 // getToolsFinder returns a ToolsFinder for the provided State. 115 // This exists for mocking. 116 var getToolsFinder = func(st *apiprovisioner.State) ToolsFinder { 117 return st 118 } 119 120 // getStartTask creates a new worker for the provisioner, 121 func (p *provisioner) getStartTask(harvestMode config.HarvestMode) (ProvisionerTask, error) { 122 auth, err := authentication.NewAPIAuthenticator(p.st) 123 if err != nil { 124 return nil, err 125 } 126 // Start responding to changes in machines, and to any further updates 127 // to the environment config. 128 machineWatcher, err := p.getMachineWatcher() 129 if err != nil { 130 return nil, err 131 } 132 retryWatcher, err := p.getRetryWatcher() 133 if err != nil && !errors.IsNotImplemented(err) { 134 return nil, err 135 } 136 tag := p.agentConfig.Tag() 137 machineTag, ok := tag.(names.MachineTag) 138 if !ok { 139 errors.Errorf("expected names.MachineTag, got %T", tag) 140 } 141 142 modelCfg, err := p.st.ModelConfig() 143 if err != nil { 144 return nil, errors.Annotate(err, "could not retrieve the model config.") 145 } 146 147 controllerCfg, err := p.st.ControllerConfig() 148 if err != nil { 149 return nil, errors.Annotate(err, "could not retrieve the controller config.") 150 } 151 152 task, err := NewProvisionerTask( 153 controllerCfg.ControllerUUID(), 154 machineTag, 155 harvestMode, 156 p.st, 157 p.toolsFinder, 158 machineWatcher, 159 retryWatcher, 160 p.broker, 161 auth, 162 modelCfg.ImageStream(), 163 RetryStrategy{retryDelay: retryStrategyDelay, retryCount: retryStrategyCount}, 164 ) 165 if err != nil { 166 return nil, errors.Trace(err) 167 } 168 return task, nil 169 } 170 171 // NewEnvironProvisioner returns a new Provisioner for an environment. 172 // When new machines are added to the state, it allocates instances 173 // from the environment and allocates them to the new machines. 174 func NewEnvironProvisioner(st *apiprovisioner.State, agentConfig agent.Config, environ environs.Environ) (Provisioner, error) { 175 p := &environProvisioner{ 176 provisioner: provisioner{ 177 st: st, 178 agentConfig: agentConfig, 179 toolsFinder: getToolsFinder(st), 180 }, 181 environ: environ, 182 } 183 p.Provisioner = p 184 p.broker = environ 185 logger.Tracef("Starting environ provisioner for %q", p.agentConfig.Tag()) 186 187 err := catacomb.Invoke(catacomb.Plan{ 188 Site: &p.catacomb, 189 Work: p.loop, 190 }) 191 if err != nil { 192 return nil, errors.Trace(err) 193 } 194 return p, nil 195 } 196 197 func (p *environProvisioner) loop() error { 198 // TODO(mjs channeling axw) - It would be better if there were 199 // APIs to watch and fetch provisioner specific config instead of 200 // watcher for all changes to model config. This would avoid the 201 // need for a full model config. 202 var modelConfigChanges <-chan struct{} 203 modelWatcher, err := p.st.WatchForModelConfigChanges() 204 if err != nil { 205 return loggedErrorStack(errors.Trace(err)) 206 } 207 if err := p.catacomb.Add(modelWatcher); err != nil { 208 return errors.Trace(err) 209 } 210 modelConfigChanges = modelWatcher.Changes() 211 212 modelConfig := p.environ.Config() 213 p.configObserver.notify(modelConfig) 214 harvestMode := modelConfig.ProvisionerHarvestMode() 215 task, err := p.getStartTask(harvestMode) 216 if err != nil { 217 return loggedErrorStack(errors.Trace(err)) 218 } 219 if err := p.catacomb.Add(task); err != nil { 220 return errors.Trace(err) 221 } 222 223 for { 224 select { 225 case <-p.catacomb.Dying(): 226 return p.catacomb.ErrDying() 227 case _, ok := <-modelConfigChanges: 228 if !ok { 229 return errors.New("model configuration watcher closed") 230 } 231 modelConfig, err := p.st.ModelConfig() 232 if err != nil { 233 return errors.Annotate(err, "cannot load model configuration") 234 } 235 if err := p.setConfig(modelConfig); err != nil { 236 return errors.Annotate(err, "loaded invalid model configuration") 237 } 238 task.SetHarvestMode(modelConfig.ProvisionerHarvestMode()) 239 } 240 } 241 } 242 243 func (p *environProvisioner) getMachineWatcher() (watcher.StringsWatcher, error) { 244 return p.st.WatchModelMachines() 245 } 246 247 func (p *environProvisioner) getRetryWatcher() (watcher.NotifyWatcher, error) { 248 return p.st.WatchMachineErrorRetry() 249 } 250 251 // setConfig updates the environment configuration and notifies 252 // the config observer. 253 func (p *environProvisioner) setConfig(modelConfig *config.Config) error { 254 if err := p.environ.SetConfig(modelConfig); err != nil { 255 return err 256 } 257 p.configObserver.notify(modelConfig) 258 return nil 259 } 260 261 // NewContainerProvisioner returns a new Provisioner. When new machines 262 // are added to the state, it allocates instances from the environment 263 // and allocates them to the new machines. 264 func NewContainerProvisioner( 265 containerType instance.ContainerType, 266 st *apiprovisioner.State, 267 agentConfig agent.Config, 268 broker environs.InstanceBroker, 269 toolsFinder ToolsFinder, 270 ) (Provisioner, error) { 271 272 p := &containerProvisioner{ 273 provisioner: provisioner{ 274 st: st, 275 agentConfig: agentConfig, 276 broker: broker, 277 toolsFinder: toolsFinder, 278 }, 279 containerType: containerType, 280 } 281 p.Provisioner = p 282 logger.Tracef("Starting %s provisioner for %q", p.containerType, p.agentConfig.Tag()) 283 284 err := catacomb.Invoke(catacomb.Plan{ 285 Site: &p.catacomb, 286 Work: p.loop, 287 }) 288 if err != nil { 289 return nil, errors.Trace(err) 290 } 291 return p, nil 292 } 293 294 func (p *containerProvisioner) loop() error { 295 modelWatcher, err := p.st.WatchForModelConfigChanges() 296 if err != nil { 297 return errors.Trace(err) 298 } 299 if err := p.catacomb.Add(modelWatcher); err != nil { 300 return errors.Trace(err) 301 } 302 303 modelConfig, err := p.st.ModelConfig() 304 if err != nil { 305 return err 306 } 307 p.configObserver.notify(modelConfig) 308 harvestMode := modelConfig.ProvisionerHarvestMode() 309 310 task, err := p.getStartTask(harvestMode) 311 if err != nil { 312 return err 313 } 314 if err := p.catacomb.Add(task); err != nil { 315 return errors.Trace(err) 316 } 317 318 for { 319 select { 320 case <-p.catacomb.Dying(): 321 return p.catacomb.ErrDying() 322 case _, ok := <-modelWatcher.Changes(): 323 if !ok { 324 return errors.New("model configuration watch closed") 325 } 326 modelConfig, err := p.st.ModelConfig() 327 if err != nil { 328 return errors.Annotate(err, "cannot load model configuration") 329 } 330 p.configObserver.notify(modelConfig) 331 task.SetHarvestMode(modelConfig.ProvisionerHarvestMode()) 332 } 333 } 334 } 335 336 func (p *containerProvisioner) getMachine() (*apiprovisioner.Machine, error) { 337 if p.machine == nil { 338 tag := p.agentConfig.Tag() 339 machineTag, ok := tag.(names.MachineTag) 340 if !ok { 341 return nil, errors.Errorf("expected names.MachineTag, got %T", tag) 342 } 343 var err error 344 if p.machine, err = p.st.Machine(machineTag); err != nil { 345 logger.Errorf("%s is not in state", machineTag) 346 return nil, err 347 } 348 } 349 return p.machine, nil 350 } 351 352 func (p *containerProvisioner) getMachineWatcher() (watcher.StringsWatcher, error) { 353 machine, err := p.getMachine() 354 if err != nil { 355 return nil, err 356 } 357 return machine.WatchContainers(p.containerType) 358 } 359 360 func (p *containerProvisioner) getRetryWatcher() (watcher.NotifyWatcher, error) { 361 return nil, errors.NotImplementedf("getRetryWatcher") 362 }