github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/apicaller/open.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apicaller 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/utils" 11 12 "github.com/juju/juju/agent" 13 "github.com/juju/juju/api" 14 apiagent "github.com/juju/juju/api/agent" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/worker" 17 ) 18 19 var ( 20 apiOpen = openAPIForAgent 21 22 checkProvisionedStrategy = utils.AttemptStrategy{ 23 Total: 1 * time.Minute, 24 Delay: 5 * time.Second, 25 } 26 ) 27 28 // openAPIForAgent exists to handle the edge case that exists 29 // when an environment is jumping several versions and doesn't 30 // yet have the environment UUID cached in the agent config. 31 // This happens only the first time an agent tries to connect 32 // after an upgrade. If there is no environment UUID set, then 33 // use login version 1. 34 func openAPIForAgent(info *api.Info, opts api.DialOpts) (api.Connection, error) { 35 if info.EnvironTag.Id() == "" { 36 return api.OpenWithVersion(info, opts, 1) 37 } 38 return api.Open(info, opts) 39 } 40 41 // OpenAPIState opens the API using the given information. The agent's 42 // password is changed if the fallback password was used to connect to 43 // the API. 44 func OpenAPIState(a agent.Agent) (_ api.Connection, _ *apiagent.Entity, err error) { 45 agentConfig := a.CurrentConfig() 46 info := agentConfig.APIInfo() 47 st, usedOldPassword, err := openAPIStateUsingInfo(info, agentConfig.OldPassword()) 48 if err != nil { 49 return nil, nil, err 50 } 51 defer func() { 52 // NOTE(fwereade): we may close and overwrite st below, 53 // so we need to double-check what we need to do here. 54 if err != nil && st != nil { 55 if err := st.Close(); err != nil { 56 logger.Errorf("while closing API connection: %v", err) 57 } 58 } 59 }() 60 61 tag := agentConfig.Tag() 62 entity, err := st.Agent().Entity(tag) 63 if err == nil && entity.Life() == params.Dead { 64 logger.Errorf("agent terminating - entity %q is dead", tag) 65 return nil, nil, worker.ErrTerminateAgent 66 } 67 if params.IsCodeUnauthorized(err) { 68 logger.Errorf("agent terminating due to error returned during entity lookup: %v", err) 69 return nil, nil, worker.ErrTerminateAgent 70 } 71 if err != nil { 72 return nil, nil, err 73 } 74 75 if !usedOldPassword { 76 // Call set password with the current password. If we've recently 77 // become a state server, this will fix up our credentials in mongo. 78 if err := entity.SetPassword(info.Password); err != nil { 79 return nil, nil, errors.Annotate(err, "can't reset agent password") 80 } 81 } else { 82 // We succeeded in connecting with the fallback 83 // password, so we need to create a new password 84 // for the future. 85 logger.Debugf("replacing insecure password") 86 newPassword, err := utils.RandomPassword() 87 if err != nil { 88 return nil, nil, err 89 } 90 err = setAgentPassword(newPassword, info.Password, a, entity) 91 if err != nil { 92 return nil, nil, err 93 } 94 95 // Reconnect to the API with the new password. 96 if err := st.Close(); err != nil { 97 logger.Errorf("while closing API connection with old password: %v", err) 98 } 99 info.Password = newPassword 100 101 // NOTE(fwereade): this is where we rebind st. If you accidentally make 102 // it a local variable you will break this func in a subtle and currently- 103 // untested way. 104 st, err = apiOpen(info, api.DialOpts{}) 105 if err != nil { 106 return nil, nil, err 107 } 108 } 109 110 return st, entity, nil 111 } 112 113 func setAgentPassword(newPw, oldPw string, a agent.Agent, entity *apiagent.Entity) error { 114 // Change the configuration *before* setting the entity 115 // password, so that we avoid the possibility that 116 // we might successfully change the entity's 117 // password but fail to write the configuration, 118 // thus locking us out completely. 119 if err := a.ChangeConfig(func(c agent.ConfigSetter) error { 120 c.SetPassword(newPw) 121 c.SetOldPassword(oldPw) 122 return nil 123 }); err != nil { 124 return err 125 } 126 return entity.SetPassword(newPw) 127 } 128 129 // OpenAPIStateUsingInfo opens the API using the given API 130 // information, and returns the opened state and the api entity with 131 // the given tag. 132 func OpenAPIStateUsingInfo(info *api.Info, oldPassword string) (api.Connection, error) { 133 st, _, err := openAPIStateUsingInfo(info, oldPassword) 134 return st, err 135 } 136 137 func openAPIStateUsingInfo(info *api.Info, oldPassword string) (api.Connection, bool, error) { 138 // We let the API dial fail immediately because the 139 // runner's loop outside the caller of openAPIState will 140 // keep on retrying. If we block for ages here, 141 // then the worker that's calling this cannot 142 // be interrupted. 143 st, err := apiOpen(info, api.DialOpts{}) 144 usedOldPassword := false 145 if params.IsCodeUnauthorized(err) { 146 // We've perhaps used the wrong password, so 147 // try again with the fallback password. 148 infoCopy := *info 149 info = &infoCopy 150 info.Password = oldPassword 151 usedOldPassword = true 152 st, err = apiOpen(info, api.DialOpts{}) 153 } 154 // The provisioner may take some time to record the agent's 155 // machine instance ID, so wait until it does so. 156 if params.IsCodeNotProvisioned(err) { 157 for a := checkProvisionedStrategy.Start(); a.Next(); { 158 st, err = apiOpen(info, api.DialOpts{}) 159 if !params.IsCodeNotProvisioned(err) { 160 break 161 } 162 } 163 } 164 if err != nil { 165 if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) { 166 logger.Errorf("agent terminating due to error returned during API open: %v", err) 167 return nil, false, worker.ErrTerminateAgent 168 } 169 return nil, false, err 170 } 171 172 return st, usedOldPassword, nil 173 }