github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/jujud/agent/agent.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 /* 5 agent contains jujud's machine agent. 6 */ 7 package agent 8 9 import ( 10 "sync" 11 "time" 12 13 "github.com/juju/cmd" 14 "github.com/juju/errors" 15 "github.com/juju/names" 16 "github.com/juju/utils" 17 "launchpad.net/gnuflag" 18 19 "github.com/juju/juju/agent" 20 "github.com/juju/juju/api" 21 apiagent "github.com/juju/juju/api/agent" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/cmd/jujud/util" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/state" 26 "github.com/juju/juju/version" 27 "github.com/juju/juju/worker" 28 ) 29 30 var ( 31 apiOpen = openAPIForAgent 32 33 checkProvisionedStrategy = utils.AttemptStrategy{ 34 Total: 1 * time.Minute, 35 Delay: 5 * time.Second, 36 } 37 ) 38 39 // openAPIForAgent exists to handle the edge case that exists 40 // when an environment is jumping several versions and doesn't 41 // yet have the environment UUID cached in the agent config. 42 // This happens only the first time an agent tries to connect 43 // after an upgrade. If there is no environment UUID set, then 44 // use login version 1. 45 func openAPIForAgent(info *api.Info, opts api.DialOpts) (*api.State, error) { 46 if info.EnvironTag.Id() == "" { 47 return api.OpenWithVersion(info, opts, 1) 48 } 49 return api.Open(info, opts) 50 } 51 52 type AgentConf interface { 53 // AddFlags injects common agent flags into f. 54 AddFlags(f *gnuflag.FlagSet) 55 // CheckArgs reports whether the given args are valid for this agent. 56 CheckArgs(args []string) error 57 // ReadConfig reads the agent's config from its config file. 58 ReadConfig(tag string) error 59 // ChangeConfig modifies this configuration using the given mutator. 60 ChangeConfig(change agent.ConfigMutator) error 61 // CurrentConfig returns the agent config for this agent. 62 CurrentConfig() agent.Config 63 // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter. 64 SetAPIHostPorts(servers [][]network.HostPort) error 65 // SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo. 66 SetStateServingInfo(info params.StateServingInfo) error 67 // DataDir returns the directory where this agent should store its data. 68 DataDir() string 69 } 70 71 // NewAgentConf returns a new value that satisfies AgentConf 72 func NewAgentConf(dataDir string) AgentConf { 73 return &agentConf{dataDir: dataDir} 74 } 75 76 // agentConf handles command-line flags shared by all agents. 77 type agentConf struct { 78 dataDir string 79 mu sync.Mutex 80 _config agent.ConfigSetterWriter 81 } 82 83 // AddFlags injects common agent flags into f. 84 func (c *agentConf) AddFlags(f *gnuflag.FlagSet) { 85 // TODO(dimitern) 2014-02-19 bug 1282025 86 // We need to pass a config location here instead and 87 // use it to locate the conf and the infer the data-dir 88 // from there instead of passing it like that. 89 f.StringVar(&c.dataDir, "data-dir", util.DataDir, "directory for juju data") 90 } 91 92 // CheckArgs reports whether the given args are valid for this agent. 93 func (c *agentConf) CheckArgs(args []string) error { 94 if c.dataDir == "" { 95 return util.RequiredError("data-dir") 96 } 97 return cmd.CheckEmpty(args) 98 } 99 100 // DataDir returns the directory where this agent should store its data. 101 func (c *agentConf) DataDir() string { 102 return c.dataDir 103 } 104 105 // ReadConfig reads the agent's config from its config file. 106 func (c *agentConf) ReadConfig(tag string) error { 107 t, err := names.ParseTag(tag) 108 if err != nil { 109 return err 110 } 111 c.mu.Lock() 112 defer c.mu.Unlock() 113 conf, err := agent.ReadConfig(agent.ConfigPath(c.dataDir, t)) 114 if err != nil { 115 return err 116 } 117 c._config = conf 118 return nil 119 } 120 121 // ChangeConfig modifies this configuration using the given mutator. 122 func (ch *agentConf) ChangeConfig(change agent.ConfigMutator) error { 123 ch.mu.Lock() 124 defer ch.mu.Unlock() 125 if err := change(ch._config); err != nil { 126 return errors.Trace(err) 127 } 128 if err := ch._config.Write(); err != nil { 129 return errors.Annotate(err, "cannot write agent configuration") 130 } 131 return nil 132 } 133 134 // CurrentConfig returns the agent config for this agent. 135 func (ch *agentConf) CurrentConfig() agent.Config { 136 ch.mu.Lock() 137 defer ch.mu.Unlock() 138 return ch._config.Clone() 139 } 140 141 // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter. 142 func (a *agentConf) SetAPIHostPorts(servers [][]network.HostPort) error { 143 return a.ChangeConfig(func(c agent.ConfigSetter) error { 144 c.SetAPIHostPorts(servers) 145 return nil 146 }) 147 } 148 149 // SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo. 150 func (a *agentConf) SetStateServingInfo(info params.StateServingInfo) error { 151 return a.ChangeConfig(func(c agent.ConfigSetter) error { 152 c.SetStateServingInfo(info) 153 return nil 154 }) 155 } 156 157 type Agent interface { 158 Tag() names.Tag 159 ChangeConfig(agent.ConfigMutator) error 160 } 161 162 // The AgentState interface is implemented by state types 163 // that represent running agents. 164 type AgentState interface { 165 // SetAgentVersion sets the tools version that the agent is 166 // currently running. 167 SetAgentVersion(v version.Binary) error 168 Tag() string 169 Life() state.Life 170 } 171 172 // isleep waits for the given duration or until it receives a value on 173 // stop. It returns whether the full duration was slept without being 174 // stopped. 175 func isleep(d time.Duration, stop <-chan struct{}) bool { 176 select { 177 case <-stop: 178 return false 179 case <-time.After(d): 180 } 181 return true 182 } 183 184 type configChanger func(c *agent.Config) 185 186 // OpenAPIState opens the API using the given information. The agent's 187 // password is changed if the fallback password was used to connect to 188 // the API. 189 func OpenAPIState(agentConfig agent.Config, a Agent) (_ *api.State, _ *apiagent.Entity, outErr error) { 190 info := agentConfig.APIInfo() 191 st, usedOldPassword, err := openAPIStateUsingInfo(info, a, agentConfig.OldPassword()) 192 if err != nil { 193 return nil, nil, err 194 } 195 defer func() { 196 if outErr != nil && st != nil { 197 st.Close() 198 } 199 }() 200 201 entity, err := st.Agent().Entity(a.Tag()) 202 if err == nil && entity.Life() == params.Dead { 203 logger.Errorf("agent terminating - entity %q is dead", a.Tag()) 204 return nil, nil, worker.ErrTerminateAgent 205 } 206 if params.IsCodeUnauthorized(err) { 207 logger.Errorf("agent terminating due to error returned during entity lookup: %v", err) 208 return nil, nil, worker.ErrTerminateAgent 209 } 210 if err != nil { 211 return nil, nil, err 212 } 213 214 if !usedOldPassword { 215 // Call set password with the current password. If we've recently 216 // become a state server, this will fix up our credentials in mongo. 217 if err := entity.SetPassword(info.Password); err != nil { 218 return nil, nil, errors.Annotate(err, "can't reset agent password") 219 } 220 } else { 221 // We succeeded in connecting with the fallback 222 // password, so we need to create a new password 223 // for the future. 224 newPassword, err := utils.RandomPassword() 225 if err != nil { 226 return nil, nil, err 227 } 228 err = setAgentPassword(newPassword, info.Password, a, entity) 229 if err != nil { 230 return nil, nil, err 231 } 232 233 // Reconnect to the API with the new password. 234 st.Close() 235 info.Password = newPassword 236 st, err = apiOpen(info, api.DialOpts{}) 237 if err != nil { 238 return nil, nil, err 239 } 240 } 241 242 return st, entity, err 243 } 244 245 func setAgentPassword(newPw, oldPw string, a Agent, entity *apiagent.Entity) error { 246 // Change the configuration *before* setting the entity 247 // password, so that we avoid the possibility that 248 // we might successfully change the entity's 249 // password but fail to write the configuration, 250 // thus locking us out completely. 251 if err := a.ChangeConfig(func(c agent.ConfigSetter) error { 252 c.SetPassword(newPw) 253 c.SetOldPassword(oldPw) 254 return nil 255 }); err != nil { 256 return err 257 } 258 return entity.SetPassword(newPw) 259 } 260 261 // OpenAPIStateUsingInfo opens the API using the given API 262 // information, and returns the opened state and the api entity with 263 // the given tag. 264 func OpenAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, error) { 265 st, _, err := openAPIStateUsingInfo(info, a, oldPassword) 266 return st, err 267 } 268 269 func openAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, bool, error) { 270 // We let the API dial fail immediately because the 271 // runner's loop outside the caller of openAPIState will 272 // keep on retrying. If we block for ages here, 273 // then the worker that's calling this cannot 274 // be interrupted. 275 st, err := apiOpen(info, api.DialOpts{}) 276 usedOldPassword := false 277 if params.IsCodeUnauthorized(err) { 278 // We've perhaps used the wrong password, so 279 // try again with the fallback password. 280 infoCopy := *info 281 info = &infoCopy 282 info.Password = oldPassword 283 usedOldPassword = true 284 st, err = apiOpen(info, api.DialOpts{}) 285 } 286 // The provisioner may take some time to record the agent's 287 // machine instance ID, so wait until it does so. 288 if params.IsCodeNotProvisioned(err) { 289 for a := checkProvisionedStrategy.Start(); a.Next(); { 290 st, err = apiOpen(info, api.DialOpts{}) 291 if !params.IsCodeNotProvisioned(err) { 292 break 293 } 294 } 295 } 296 if err != nil { 297 if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) { 298 logger.Errorf("agent terminating due to error returned during API open: %v", err) 299 return nil, false, worker.ErrTerminateAgent 300 } 301 return nil, false, err 302 } 303 304 return st, usedOldPassword, nil 305 }