github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 = api.Open 32 33 checkProvisionedStrategy = utils.AttemptStrategy{ 34 Total: 1 * time.Minute, 35 Delay: 5 * time.Second, 36 } 37 ) 38 39 // AgentConf handles command-line flags shared by all agents. 40 type AgentConf struct { 41 DataDir string 42 mu sync.Mutex 43 _config agent.ConfigSetterWriter 44 } 45 46 type AgentConfigMutator func(agent.ConfigSetter) error 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", util.DataDir, "directory for juju data") 55 } 56 57 func (c *AgentConf) CheckArgs(args []string) error { 58 if c.DataDir == "" { 59 return util.RequiredError("data-dir") 60 } 61 return cmd.CheckEmpty(args) 62 } 63 64 func (c *AgentConf) ReadConfig(tag string) error { 65 t, err := names.ParseTag(tag) 66 if err != nil { 67 return err 68 } 69 c.mu.Lock() 70 defer c.mu.Unlock() 71 conf, err := agent.ReadConfig(agent.ConfigPath(c.DataDir, t)) 72 if err != nil { 73 return err 74 } 75 c._config = conf 76 return nil 77 } 78 79 func (ch *AgentConf) ChangeConfig(change AgentConfigMutator) error { 80 ch.mu.Lock() 81 defer ch.mu.Unlock() 82 if err := change(ch._config); err != nil { 83 return errors.Trace(err) 84 } 85 if err := ch._config.Write(); err != nil { 86 return errors.Annotate(err, "cannot write agent configuration") 87 } 88 return nil 89 } 90 91 func (ch *AgentConf) CurrentConfig() agent.Config { 92 ch.mu.Lock() 93 defer ch.mu.Unlock() 94 return ch._config.Clone() 95 } 96 97 // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter. 98 func (a *AgentConf) SetAPIHostPorts(servers [][]network.HostPort) error { 99 return a.ChangeConfig(func(c agent.ConfigSetter) error { 100 c.SetAPIHostPorts(servers) 101 return nil 102 }) 103 } 104 105 // SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo. 106 func (a *AgentConf) SetStateServingInfo(info params.StateServingInfo) error { 107 return a.ChangeConfig(func(c agent.ConfigSetter) error { 108 c.SetStateServingInfo(info) 109 return nil 110 }) 111 } 112 113 type Agent interface { 114 Tag() names.Tag 115 ChangeConfig(AgentConfigMutator) error 116 } 117 118 // The AgentState interface is implemented by state types 119 // that represent running agents. 120 type AgentState interface { 121 // SetAgentVersion sets the tools version that the agent is 122 // currently running. 123 SetAgentVersion(v version.Binary) error 124 Tag() string 125 Life() state.Life 126 } 127 128 // isleep waits for the given duration or until it receives a value on 129 // stop. It returns whether the full duration was slept without being 130 // stopped. 131 func isleep(d time.Duration, stop <-chan struct{}) bool { 132 select { 133 case <-stop: 134 return false 135 case <-time.After(d): 136 } 137 return true 138 } 139 140 type apiOpener interface { 141 OpenAPI(api.DialOpts) (*api.State, string, error) 142 } 143 144 type configChanger func(c *agent.Config) 145 146 // OpenAPIState opens the API using the given information. The agent's 147 // password is changed if the fallback password was used to connect to 148 // the API. 149 func OpenAPIState(agentConfig agent.Config, a Agent) (_ *api.State, _ *apiagent.Entity, outErr error) { 150 info := agentConfig.APIInfo() 151 st, usedOldPassword, err := openAPIStateUsingInfo(info, a, agentConfig.OldPassword()) 152 if err != nil { 153 return nil, nil, err 154 } 155 defer func() { 156 if outErr != nil && st != nil { 157 st.Close() 158 } 159 }() 160 161 entity, err := st.Agent().Entity(a.Tag()) 162 if err == nil && entity.Life() == params.Dead { 163 logger.Errorf("agent terminating - entity %q is dead", a.Tag()) 164 return nil, nil, worker.ErrTerminateAgent 165 } 166 if params.IsCodeUnauthorized(err) { 167 logger.Errorf("agent terminating due to error returned during entity lookup: %v", err) 168 return nil, nil, worker.ErrTerminateAgent 169 } 170 if err != nil { 171 return nil, nil, err 172 } 173 174 if usedOldPassword { 175 // We succeeded in connecting with the fallback 176 // password, so we need to create a new password 177 // for the future. 178 newPassword, err := utils.RandomPassword() 179 if err != nil { 180 return nil, nil, err 181 } 182 // Change the configuration *before* setting the entity 183 // password, so that we avoid the possibility that 184 // we might successfully change the entity's 185 // password but fail to write the configuration, 186 // thus locking us out completely. 187 if err := a.ChangeConfig(func(c agent.ConfigSetter) error { 188 c.SetPassword(newPassword) 189 c.SetOldPassword(info.Password) 190 return nil 191 }); err != nil { 192 return nil, nil, err 193 } 194 if err := entity.SetPassword(newPassword); err != nil { 195 return nil, nil, err 196 } 197 198 // Reconnect to the API with the new password. 199 st.Close() 200 info.Password = newPassword 201 st, err = apiOpen(info, api.DialOpts{}) 202 if err != nil { 203 return nil, nil, err 204 } 205 } 206 207 return st, entity, err 208 } 209 210 // OpenAPIStateUsingInfo opens the API using the given API 211 // information, and returns the opened state and the api entity with 212 // the given tag. 213 func OpenAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, error) { 214 st, _, err := openAPIStateUsingInfo(info, a, oldPassword) 215 return st, err 216 } 217 218 func openAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, bool, error) { 219 // We let the API dial fail immediately because the 220 // runner's loop outside the caller of openAPIState will 221 // keep on retrying. If we block for ages here, 222 // then the worker that's calling this cannot 223 // be interrupted. 224 st, err := apiOpen(info, api.DialOpts{}) 225 usedOldPassword := false 226 if params.IsCodeUnauthorized(err) { 227 // We've perhaps used the wrong password, so 228 // try again with the fallback password. 229 infoCopy := *info 230 info = &infoCopy 231 info.Password = oldPassword 232 usedOldPassword = true 233 st, err = apiOpen(info, api.DialOpts{}) 234 } 235 // The provisioner may take some time to record the agent's 236 // machine instance ID, so wait until it does so. 237 if params.IsCodeNotProvisioned(err) { 238 for a := checkProvisionedStrategy.Start(); a.Next(); { 239 st, err = apiOpen(info, api.DialOpts{}) 240 if !params.IsCodeNotProvisioned(err) { 241 break 242 } 243 } 244 } 245 if err != nil { 246 if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) { 247 logger.Errorf("agent terminating due to error returned during API open: %v", err) 248 return nil, false, worker.ErrTerminateAgent 249 } 250 return nil, false, err 251 } 252 253 return st, usedOldPassword, nil 254 }