github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/names" 12 "launchpad.net/tomb" 13 14 "github.com/juju/juju/agent" 15 apiprovisioner "github.com/juju/juju/api/provisioner" 16 apiwatcher "github.com/juju/juju/api/watcher" 17 "github.com/juju/juju/environmentserver/authentication" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/state/watcher" 22 "github.com/juju/juju/worker" 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 // Provisioner represents a running provisioner worker. 32 type Provisioner interface { 33 worker.Worker 34 Stop() error 35 getMachineWatcher() (apiwatcher.StringsWatcher, error) 36 getRetryWatcher() (apiwatcher.NotifyWatcher, error) 37 } 38 39 // environProvisioner represents a running provisioning worker for machine nodes 40 // belonging to an environment. 41 type environProvisioner struct { 42 provisioner 43 environ environs.Environ 44 configObserver 45 } 46 47 // containerProvisioner represents a running provisioning worker for containers 48 // hosted on a machine. 49 type containerProvisioner struct { 50 provisioner 51 containerType instance.ContainerType 52 machine *apiprovisioner.Machine 53 } 54 55 // provisioner providers common behaviour for a running provisioning worker. 56 type provisioner struct { 57 Provisioner 58 st *apiprovisioner.State 59 agentConfig agent.Config 60 broker environs.InstanceBroker 61 tomb tomb.Tomb 62 } 63 64 // configObserver is implemented so that tests can see 65 // when the environment configuration changes. 66 type configObserver struct { 67 sync.Mutex 68 observer chan<- *config.Config 69 } 70 71 // notify notifies the observer of a configuration change. 72 func (o *configObserver) notify(cfg *config.Config) { 73 o.Lock() 74 if o.observer != nil { 75 o.observer <- cfg 76 } 77 o.Unlock() 78 } 79 80 // Err returns the reason why the provisioner has stopped or tomb.ErrStillAlive 81 // when it is still alive. 82 func (p *provisioner) Err() (reason error) { 83 return p.tomb.Err() 84 } 85 86 // Kill implements worker.Worker.Kill. 87 func (p *provisioner) Kill() { 88 p.tomb.Kill(nil) 89 } 90 91 // Wait implements worker.Worker.Wait. 92 func (p *provisioner) Wait() error { 93 return p.tomb.Wait() 94 } 95 96 // Stop stops the provisioner and returns any error encountered while 97 // provisioning. 98 func (p *provisioner) Stop() error { 99 p.tomb.Kill(nil) 100 return p.tomb.Wait() 101 } 102 103 // getToolsFinder returns a ToolsFinder for the provided State. 104 // This exists for mocking. 105 var getToolsFinder = func(st *apiprovisioner.State) ToolsFinder { 106 return st 107 } 108 109 // getStartTask creates a new worker for the provisioner, 110 func (p *provisioner) getStartTask(harvestMode config.HarvestMode) (ProvisionerTask, error) { 111 auth, err := authentication.NewAPIAuthenticator(p.st) 112 if err != nil { 113 return nil, err 114 } 115 // Start responding to changes in machines, and to any further updates 116 // to the environment config. 117 machineWatcher, err := p.getMachineWatcher() 118 if err != nil { 119 return nil, err 120 } 121 retryWatcher, err := p.getRetryWatcher() 122 if err != nil && !errors.IsNotImplemented(err) { 123 return nil, err 124 } 125 tag := p.agentConfig.Tag() 126 machineTag, ok := tag.(names.MachineTag) 127 if !ok { 128 errors.Errorf("expacted names.MachineTag, got %T", tag) 129 } 130 131 envCfg, err := p.st.EnvironConfig() 132 if err != nil { 133 return nil, errors.Annotate(err, "could not retrieve the environment config.") 134 } 135 136 secureServerConnection := false 137 if info, ok := p.agentConfig.StateServingInfo(); ok { 138 secureServerConnection = info.CAPrivateKey != "" 139 } 140 task := NewProvisionerTask( 141 machineTag, 142 harvestMode, 143 p.st, 144 getToolsFinder(p.st), 145 machineWatcher, 146 retryWatcher, 147 p.broker, 148 auth, 149 envCfg.ImageStream(), 150 secureServerConnection, 151 ) 152 return task, nil 153 } 154 155 // NewEnvironProvisioner returns a new Provisioner for an environment. 156 // When new machines are added to the state, it allocates instances 157 // from the environment and allocates them to the new machines. 158 func NewEnvironProvisioner(st *apiprovisioner.State, agentConfig agent.Config) Provisioner { 159 p := &environProvisioner{ 160 provisioner: provisioner{ 161 st: st, 162 agentConfig: agentConfig, 163 }, 164 } 165 p.Provisioner = p 166 logger.Tracef("Starting environ provisioner for %q", p.agentConfig.Tag()) 167 go func() { 168 defer p.tomb.Done() 169 p.tomb.Kill(p.loop()) 170 }() 171 return p 172 } 173 174 func (p *environProvisioner) loop() error { 175 var environConfigChanges <-chan struct{} 176 environWatcher, err := p.st.WatchForEnvironConfigChanges() 177 if err != nil { 178 return err 179 } 180 environConfigChanges = environWatcher.Changes() 181 defer watcher.Stop(environWatcher, &p.tomb) 182 183 p.environ, err = worker.WaitForEnviron(environWatcher, p.st, p.tomb.Dying()) 184 if err != nil { 185 return err 186 } 187 p.broker = p.environ 188 189 harvestMode := p.environ.Config().ProvisionerHarvestMode() 190 task, err := p.getStartTask(harvestMode) 191 if err != nil { 192 return err 193 } 194 defer watcher.Stop(task, &p.tomb) 195 196 for { 197 select { 198 case <-p.tomb.Dying(): 199 return tomb.ErrDying 200 case <-task.Dying(): 201 err := task.Err() 202 logger.Errorf("environ provisioner died: %v", err) 203 return err 204 case _, ok := <-environConfigChanges: 205 if !ok { 206 return watcher.EnsureErr(environWatcher) 207 } 208 environConfig, err := p.st.EnvironConfig() 209 if err != nil { 210 logger.Errorf("cannot load environment configuration: %v", err) 211 return err 212 } 213 if err := p.setConfig(environConfig); err != nil { 214 logger.Errorf("loaded invalid environment configuration: %v", err) 215 } 216 task.SetHarvestMode(environConfig.ProvisionerHarvestMode()) 217 } 218 } 219 } 220 221 func (p *environProvisioner) getMachineWatcher() (apiwatcher.StringsWatcher, error) { 222 return p.st.WatchEnvironMachines() 223 } 224 225 func (p *environProvisioner) getRetryWatcher() (apiwatcher.NotifyWatcher, error) { 226 return p.st.WatchMachineErrorRetry() 227 } 228 229 // setConfig updates the environment configuration and notifies 230 // the config observer. 231 func (p *environProvisioner) setConfig(environConfig *config.Config) error { 232 if err := p.environ.SetConfig(environConfig); err != nil { 233 return err 234 } 235 p.configObserver.notify(environConfig) 236 return nil 237 } 238 239 // NewContainerProvisioner returns a new Provisioner. When new machines 240 // are added to the state, it allocates instances from the environment 241 // and allocates them to the new machines. 242 func NewContainerProvisioner(containerType instance.ContainerType, st *apiprovisioner.State, 243 agentConfig agent.Config, broker environs.InstanceBroker) Provisioner { 244 245 p := &containerProvisioner{ 246 provisioner: provisioner{ 247 st: st, 248 agentConfig: agentConfig, 249 broker: broker, 250 }, 251 containerType: containerType, 252 } 253 p.Provisioner = p 254 logger.Tracef("Starting %s provisioner for %q", p.containerType, p.agentConfig.Tag()) 255 go func() { 256 defer p.tomb.Done() 257 p.tomb.Kill(p.loop()) 258 }() 259 return p 260 } 261 262 func (p *containerProvisioner) loop() error { 263 task, err := p.getStartTask(config.HarvestDestroyed) 264 if err != nil { 265 return err 266 } 267 defer watcher.Stop(task, &p.tomb) 268 269 for { 270 select { 271 case <-p.tomb.Dying(): 272 return tomb.ErrDying 273 case <-task.Dying(): 274 err := task.Err() 275 logger.Errorf("%s provisioner died: %v", p.containerType, err) 276 return err 277 } 278 } 279 } 280 281 func (p *containerProvisioner) getMachine() (*apiprovisioner.Machine, error) { 282 if p.machine == nil { 283 tag := p.agentConfig.Tag() 284 machineTag, ok := tag.(names.MachineTag) 285 if !ok { 286 return nil, errors.Errorf("expected names.MachineTag, got %T", tag) 287 } 288 var err error 289 if p.machine, err = p.st.Machine(machineTag); err != nil { 290 logger.Errorf("%s is not in state", machineTag) 291 return nil, err 292 } 293 } 294 return p.machine, nil 295 } 296 297 func (p *containerProvisioner) getMachineWatcher() (apiwatcher.StringsWatcher, error) { 298 machine, err := p.getMachine() 299 if err != nil { 300 return nil, err 301 } 302 return machine.WatchContainers(p.containerType) 303 } 304 305 func (p *containerProvisioner) getRetryWatcher() (apiwatcher.NotifyWatcher, error) { 306 return nil, errors.NotImplementedf("getRetryWatcher") 307 }