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