github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/cmd/jujud/agent.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package main 5 6 import ( 7 "fmt" 8 "io" 9 "path/filepath" 10 "sync" 11 "time" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/utils" 16 "github.com/juju/utils/fslock" 17 "launchpad.net/gnuflag" 18 19 "github.com/juju/juju/agent" 20 "github.com/juju/juju/network" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/state/api" 23 apiagent "github.com/juju/juju/state/api/agent" 24 apideployer "github.com/juju/juju/state/api/deployer" 25 "github.com/juju/juju/state/api/params" 26 apirsyslog "github.com/juju/juju/state/api/rsyslog" 27 "github.com/juju/juju/version" 28 "github.com/juju/juju/worker" 29 "github.com/juju/juju/worker/deployer" 30 "github.com/juju/juju/worker/rsyslog" 31 "github.com/juju/juju/worker/upgrader" 32 ) 33 34 var apiOpen = api.Open 35 36 // requiredError is useful when complaining about missing command-line options. 37 func requiredError(name string) error { 38 return fmt.Errorf("--%s option must be set", name) 39 } 40 41 // AgentConf handles command-line flags shared by all agents. 42 type AgentConf struct { 43 dataDir string 44 mu sync.Mutex 45 _config agent.ConfigSetterWriter 46 } 47 48 // AddFlags injects common agent flags into f. 49 func (c *AgentConf) AddFlags(f *gnuflag.FlagSet) { 50 // TODO(dimitern) 2014-02-19 bug 1282025 51 // We need to pass a config location here instead and 52 // use it to locate the conf and the infer the data-dir 53 // from there instead of passing it like that. 54 f.StringVar(&c.dataDir, "data-dir", "/var/lib/juju", "directory for juju data") 55 } 56 57 func (c *AgentConf) CheckArgs(args []string) error { 58 if c.dataDir == "" { 59 return requiredError("data-dir") 60 } 61 return cmd.CheckEmpty(args) 62 } 63 64 func (c *AgentConf) ReadConfig(tag string) error { 65 c.mu.Lock() 66 defer c.mu.Unlock() 67 conf, err := agent.ReadConfig(agent.ConfigPath(c.dataDir, tag)) 68 if err != nil { 69 return err 70 } 71 c._config = conf 72 return nil 73 } 74 75 func (ch *AgentConf) ChangeConfig(change func(c agent.ConfigSetter)) error { 76 ch.mu.Lock() 77 defer ch.mu.Unlock() 78 change(ch._config) 79 return ch._config.Write() 80 } 81 82 func (ch *AgentConf) CurrentConfig() agent.Config { 83 ch.mu.Lock() 84 defer ch.mu.Unlock() 85 return ch._config.Clone() 86 } 87 88 // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter. 89 func (a *AgentConf) SetAPIHostPorts(servers [][]network.HostPort) error { 90 return a.ChangeConfig(func(c agent.ConfigSetter) { 91 c.SetAPIHostPorts(servers) 92 }) 93 } 94 95 func importance(err error) int { 96 switch { 97 case err == nil: 98 return 0 99 default: 100 return 1 101 case isUpgraded(err): 102 return 2 103 case err == worker.ErrTerminateAgent: 104 return 3 105 } 106 } 107 108 // moreImportant returns whether err0 is 109 // more important than err1 - that is, whether 110 // we should act on err0 in preference to err1. 111 func moreImportant(err0, err1 error) bool { 112 return importance(err0) > importance(err1) 113 } 114 115 func isUpgraded(err error) bool { 116 _, ok := err.(*upgrader.UpgradeReadyError) 117 return ok 118 } 119 120 type Agent interface { 121 Tag() string 122 ChangeConfig(func(agent.ConfigSetter)) error 123 } 124 125 // The AgentState interface is implemented by state types 126 // that represent running agents. 127 type AgentState interface { 128 // SetAgentVersion sets the tools version that the agent is 129 // currently running. 130 SetAgentVersion(v version.Binary) error 131 Tag() string 132 SetMongoPassword(password string) error 133 Life() state.Life 134 } 135 136 type fatalError struct { 137 Err string 138 } 139 140 func (e *fatalError) Error() string { 141 return e.Err 142 } 143 144 func isFatal(err error) bool { 145 if err == worker.ErrTerminateAgent { 146 return true 147 } 148 if isUpgraded(err) { 149 return true 150 } 151 _, ok := err.(*fatalError) 152 return ok 153 } 154 155 type pinger interface { 156 Ping() error 157 } 158 159 // connectionIsFatal returns a function suitable for passing 160 // as the isFatal argument to worker.NewRunner, 161 // that diagnoses an error as fatal if the connection 162 // has failed or if the error is otherwise fatal. 163 func connectionIsFatal(conn pinger) func(err error) bool { 164 return func(err error) bool { 165 if isFatal(err) { 166 return true 167 } 168 if err := conn.Ping(); err != nil { 169 logger.Infof("error pinging %T: %v", conn, err) 170 return true 171 } 172 return false 173 } 174 } 175 176 // isleep waits for the given duration or until it receives a value on 177 // stop. It returns whether the full duration was slept without being 178 // stopped. 179 func isleep(d time.Duration, stop <-chan struct{}) bool { 180 select { 181 case <-stop: 182 return false 183 case <-time.After(d): 184 } 185 return true 186 } 187 188 type apiOpener interface { 189 OpenAPI(api.DialOpts) (*api.State, string, error) 190 } 191 192 type configChanger func(c *agent.Config) 193 194 // openAPIState opens the API using the given information, and 195 // returns the opened state and the api entity with 196 // the given tag. The given changeConfig function is 197 // called if the password changes to set the password. 198 func openAPIState( 199 agentConfig agent.Config, 200 a Agent, 201 ) (_ *api.State, _ *apiagent.Entity, err error) { 202 // We let the API dial fail immediately because the 203 // runner's loop outside the caller of openAPIState will 204 // keep on retrying. If we block for ages here, 205 // then the worker that's calling this cannot 206 // be interrupted. 207 info := agentConfig.APIInfo() 208 st, err := apiOpen(info, api.DialOpts{}) 209 usedOldPassword := false 210 if params.IsCodeUnauthorized(err) { 211 // We've perhaps used the wrong password, so 212 // try again with the fallback password. 213 info := *info 214 info.Password = agentConfig.OldPassword() 215 usedOldPassword = true 216 st, err = apiOpen(&info, api.DialOpts{}) 217 } 218 if err != nil { 219 if params.IsCodeNotProvisioned(err) { 220 return nil, nil, worker.ErrTerminateAgent 221 } 222 if params.IsCodeUnauthorized(err) { 223 return nil, nil, worker.ErrTerminateAgent 224 } 225 return nil, nil, err 226 } 227 defer func() { 228 if err != nil { 229 st.Close() 230 } 231 }() 232 entity, err := st.Agent().Entity(a.Tag()) 233 if err == nil && entity.Life() == params.Dead { 234 return nil, nil, worker.ErrTerminateAgent 235 } 236 if err != nil { 237 if params.IsCodeUnauthorized(err) { 238 return nil, nil, worker.ErrTerminateAgent 239 } 240 return nil, nil, err 241 } 242 if usedOldPassword { 243 // We succeeded in connecting with the fallback 244 // password, so we need to create a new password 245 // for the future. 246 247 newPassword, err := utils.RandomPassword() 248 if err != nil { 249 return nil, nil, err 250 } 251 // Change the configuration *before* setting the entity 252 // password, so that we avoid the possibility that 253 // we might successfully change the entity's 254 // password but fail to write the configuration, 255 // thus locking us out completely. 256 if err := a.ChangeConfig(func(c agent.ConfigSetter) { 257 c.SetPassword(newPassword) 258 c.SetOldPassword(info.Password) 259 }); err != nil { 260 return nil, nil, err 261 } 262 if err := entity.SetPassword(newPassword); err != nil { 263 return nil, nil, err 264 } 265 } 266 267 return st, entity, nil 268 } 269 270 // agentDone processes the error returned by 271 // an exiting agent. 272 func agentDone(err error) error { 273 if err == worker.ErrTerminateAgent { 274 err = nil 275 } 276 if ug, ok := err.(*upgrader.UpgradeReadyError); ok { 277 if err := ug.ChangeAgentTools(); err != nil { 278 // Return and let upstart deal with the restart. 279 return errors.LoggedErrorf(logger, "cannot change agent tools: %v", err) 280 } 281 } 282 return err 283 } 284 285 type closeWorker struct { 286 worker worker.Worker 287 closer io.Closer 288 } 289 290 // newCloseWorker returns a task that wraps the given task, 291 // closing the given closer when it finishes. 292 func newCloseWorker(worker worker.Worker, closer io.Closer) worker.Worker { 293 return &closeWorker{ 294 worker: worker, 295 closer: closer, 296 } 297 } 298 299 func (c *closeWorker) Kill() { 300 c.worker.Kill() 301 } 302 303 func (c *closeWorker) Wait() error { 304 err := c.worker.Wait() 305 if err := c.closer.Close(); err != nil { 306 logger.Errorf("closeWorker: close error: %v", err) 307 } 308 return err 309 } 310 311 // newDeployContext gives the tests the opportunity to create a deployer.Context 312 // that can be used for testing so as to avoid (1) deploying units to the system 313 // running the tests and (2) get access to the *State used internally, so that 314 // tests can be run without waiting for the 5s watcher refresh time to which we would 315 // otherwise be restricted. 316 var newDeployContext = func(st *apideployer.State, agentConfig agent.Config) deployer.Context { 317 return deployer.NewSimpleContext(agentConfig, st) 318 } 319 320 // newRsyslogConfigWorker creates and returns a new RsyslogConfigWorker 321 // based on the specified configuration parameters. 322 var newRsyslogConfigWorker = func(st *apirsyslog.State, agentConfig agent.Config, mode rsyslog.RsyslogMode) (worker.Worker, error) { 323 tag := agentConfig.Tag() 324 namespace := agentConfig.Value(agent.Namespace) 325 addrs, err := agentConfig.APIAddresses() 326 if err != nil { 327 return nil, err 328 } 329 return rsyslog.NewRsyslogConfigWorker(st, mode, tag, namespace, addrs) 330 } 331 332 // hookExecutionLock returns an *fslock.Lock suitable for use as a unit 333 // hook execution lock. Other workers may also use this lock if they 334 // require isolation from hook execution. 335 func hookExecutionLock(dataDir string) (*fslock.Lock, error) { 336 lockDir := filepath.Join(dataDir, "locks") 337 return fslock.NewLock(lockDir, "uniter-hook-execution") 338 }