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