github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/backups/restore.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // +build !windows 5 6 package backups 7 8 import ( 9 "bytes" 10 "fmt" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "sync" 16 "text/template" 17 "time" 18 19 "github.com/juju/errors" 20 "github.com/juju/names" 21 "github.com/juju/utils" 22 "github.com/juju/utils/symlink" 23 "gopkg.in/mgo.v2" 24 "gopkg.in/mgo.v2/bson" 25 26 "github.com/juju/juju/agent" 27 "github.com/juju/juju/agent/tools" 28 "github.com/juju/juju/environs" 29 "github.com/juju/juju/instance" 30 "github.com/juju/juju/mongo" 31 "github.com/juju/juju/network" 32 "github.com/juju/juju/state" 33 "github.com/juju/juju/utils/ssh" 34 "github.com/juju/juju/worker/peergrouper" 35 ) 36 37 // TODO(perrito666) create an authoritative source for all possible 38 // uses of this const, not only here but all around juju 39 const restoreUserHome = "/home/ubuntu/" 40 41 // resetReplicaSet re-initiates replica-set using the new state server 42 // values, this is required after a mongo restore. 43 // In case of failure returns error. 44 func resetReplicaSet(dialInfo *mgo.DialInfo, memberHostPort string) error { 45 params := peergrouper.InitiateMongoParams{dialInfo, 46 memberHostPort, 47 dialInfo.Username, 48 dialInfo.Password, 49 } 50 return peergrouper.InitiateMongoServer(params, true) 51 } 52 53 var filesystemRoot = getFilesystemRoot 54 55 func getFilesystemRoot() string { 56 return string(os.PathSeparator) 57 } 58 59 // newDialInfo returns mgo.DialInfo with the given address using the minimal 60 // possible setup. 61 func newDialInfo(privateAddr string, conf agent.Config) (*mgo.DialInfo, error) { 62 dialOpts := mongo.DialOpts{Direct: true} 63 ssi, ok := conf.StateServingInfo() 64 if !ok { 65 return nil, errors.Errorf("cannot get state serving info to dial") 66 } 67 info := mongo.Info{ 68 Addrs: []string{fmt.Sprintf("%s:%d", privateAddr, ssi.StatePort)}, 69 CACert: conf.CACert(), 70 } 71 dialInfo, err := mongo.DialInfo(info, dialOpts) 72 if err != nil { 73 return nil, errors.Annotate(err, "cannot produce a dial info") 74 } 75 dialInfo.Username = "admin" 76 dialInfo.Password = conf.OldPassword() 77 return dialInfo, nil 78 } 79 80 // updateMongoEntries will update the machine entries in the restored mongo to 81 // reflect the real machine instanceid in case it changed (a newly bootstraped 82 // server). 83 func updateMongoEntries(newInstId instance.Id, newMachineId string, dialInfo *mgo.DialInfo) error { 84 session, err := mgo.DialWithInfo(dialInfo) 85 if err != nil { 86 return errors.Annotate(err, "cannot connect to mongo to update") 87 } 88 defer session.Close() 89 // TODO(perrito666): Take the Machine id from an autoritative source 90 err = session.DB("juju").C("machines").Update( 91 bson.M{"machineid": newMachineId}, 92 bson.M{"$set": bson.M{"instanceid": string(newInstId)}}, 93 ) 94 if err != nil { 95 return errors.Annotatef(err, "cannot update machine %s instance information", newMachineId) 96 } 97 return nil 98 } 99 100 // assign to variables for testing purposes. 101 var mongoDefaultDialOpts = mongo.DefaultDialOpts 102 var environsNewStatePolicy = environs.NewStatePolicy 103 104 // newStateConnection tries to connect to the newly restored state server. 105 func newStateConnection(info *mongo.MongoInfo) (*state.State, error) { 106 // We need to retry here to allow mongo to come up on the restored state server. 107 // The connection might succeed due to the mongo dial retries but there may still 108 // be a problem issuing database commands. 109 var ( 110 st *state.State 111 err error 112 ) 113 const ( 114 newStateConnDelay = 15 * time.Second 115 newStateConnMinAttempts = 8 116 ) 117 attempt := utils.AttemptStrategy{Delay: newStateConnDelay, Min: newStateConnMinAttempts} 118 for a := attempt.Start(); a.Next(); { 119 st, err = state.Open(info, mongoDefaultDialOpts(), environsNewStatePolicy()) 120 if err == nil { 121 return st, nil 122 } 123 logger.Errorf("cannot open state, retrying: %v", err) 124 } 125 return st, errors.Annotate(err, "cannot open state") 126 } 127 128 // updateAllMachines finds all machines and resets the stored state address 129 // in each of them. The address does not include the port. 130 // It is too late to go back and errors in a couple of agents have 131 // better chance of being fixed by the user, if we were to fail 132 // we risk an inconsistent state server because of one unresponsive 133 // agent, we should nevertheless return the err info to the user. 134 func updateAllMachines(privateAddress string, machines []*state.Machine) error { 135 var machineUpdating sync.WaitGroup 136 for key := range machines { 137 // key is used to have machine be scope bound to the loop iteration. 138 machine := machines[key] 139 // A newly resumed state server requires no updating, and more 140 // than one state server is not yet supported by this code. 141 if machine.IsManager() || machine.Life() == state.Dead { 142 continue 143 } 144 machineUpdating.Add(1) 145 go func() { 146 defer machineUpdating.Done() 147 err := runMachineUpdate(machine.Addresses(), setAgentAddressScript(privateAddress)) 148 logger.Errorf("failed updating machine: %v", err) 149 }() 150 } 151 machineUpdating.Wait() 152 153 // We should return errors encapsulated in a digest. 154 return nil 155 } 156 157 // agentAddressAndRelationsTemplate is the template used to replace the api server data 158 // in the agents for the new ones if the machine has been rebootstraped it will also reset 159 // the relations so hooks will re-fire. 160 var agentAddressAndRelationsTemplate = template.Must(template.New("").Parse(` 161 set -xu 162 cd /var/lib/juju/agents 163 for agent in * 164 do 165 status jujud-$agent| grep -q "^jujud-$agent start" > /dev/null 166 if [ $? -eq 0 ]; then 167 initctl stop jujud-$agent 168 fi 169 sed -i.old -r "/^(stateaddresses|apiaddresses):/{ 170 n 171 s/- .*(:[0-9]+)/- {{.Address}}\1/ 172 }" $agent/agent.conf 173 174 # If we're processing a unit agent's directly 175 # and it has some relations, reset 176 # the stored version of all of them to 177 # ensure that any relation hooks will 178 # fire. 179 if [[ $agent = unit-* ]] 180 then 181 find $agent/state/relations -type f -exec sed -i -r 's/change-version: [0-9]+$/change-version: 0/' {} \; 182 fi 183 # Just in case is a stale unit 184 status jujud-$agent| grep -q "^jujud-$agent stop" > /dev/null 185 if [ $? -eq 0 ]; then 186 initctl start jujud-$agent 187 fi 188 done 189 `)) 190 191 // setAgentAddressScript generates an ssh script argument to update state addresses. 192 func setAgentAddressScript(stateAddr string) string { 193 var buf bytes.Buffer 194 err := agentAddressAndRelationsTemplate.Execute(&buf, struct { 195 Address string 196 }{stateAddr}) 197 if err != nil { 198 panic(errors.Annotate(err, "template error")) 199 } 200 return buf.String() 201 } 202 203 // runMachineUpdate connects via ssh to the machine and runs the update script. 204 func runMachineUpdate(allAddr []network.Address, sshArg string) error { 205 addr := network.SelectPublicAddress(allAddr) 206 if addr == "" { 207 return errors.Errorf("no appropriate public address found") 208 } 209 return runViaSSH(addr, sshArg) 210 } 211 212 // sshCommand hods ssh.Command type for testing purposes. 213 var sshCommand = ssh.Command 214 215 // runViaSSH runs script in the remote machine with address addr. 216 func runViaSSH(addr string, script string) error { 217 // This is taken from cmd/juju/ssh.go there is no other clear way to set user 218 userAddr := "ubuntu@" + addr 219 sshOptions := ssh.Options{} 220 sshOptions.SetIdentities("/var/lib/juju/system-identity") 221 userCmd := sshCommand(userAddr, []string{"sudo", "-n", "bash", "-c " + utils.ShQuote(script)}, &sshOptions) 222 var stderrBuf bytes.Buffer 223 userCmd.Stderr = &stderrBuf 224 if err := userCmd.Run(); err != nil { 225 return errors.Annotatef(err, "ssh command failed: %q", stderrBuf.String()) 226 } 227 return nil 228 } 229 230 func updateBackupMachineTag(oldTag, newTag names.Tag) error { 231 oldTagString := oldTag.String() 232 newTagString := newTag.String() 233 234 if oldTagString == newTagString { 235 return nil 236 } 237 oldTagPath := path.Join(agent.DefaultDataDir, oldTagString) 238 newTagPath := path.Join(agent.DefaultDataDir, newTagString) 239 240 oldToolsDir := tools.ToolsDir(agent.DefaultDataDir, oldTagString) 241 oldLink, err := filepath.EvalSymlinks(oldToolsDir) 242 243 os.Rename(oldTagPath, newTagPath) 244 newToolsDir := tools.ToolsDir(agent.DefaultDataDir, newTagString) 245 newToolsPath := strings.Replace(oldLink, oldTagPath, newTagPath, -1) 246 err = symlink.Replace(newToolsDir, newToolsPath) 247 return errors.Annotatef(err, "cannot set the new tools path") 248 }