github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/backups/restore.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "fmt" 8 "os" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/mongo" 15 "github.com/juju/juju/service" 16 "github.com/juju/juju/service/common" 17 "github.com/juju/juju/state" 18 "github.com/juju/juju/state/backups" 19 ) 20 21 var bootstrapNode = names.NewMachineTag("0") 22 23 // Restore implements the server side of Backups.Restore. 24 func (a *API) Restore(p params.RestoreArgs) error { 25 logger.Infof("Starting server side restore") 26 27 // Get hold of a backup file Reader 28 backup, closer := newBackups(a.backend) 29 defer closer.Close() 30 31 // Obtain the address of current machine, where we will be performing restore. 32 machine, err := a.backend.Machine(a.machineID) 33 if err != nil { 34 return errors.Trace(err) 35 } 36 37 addr, err := machine.PrivateAddress() 38 if err != nil { 39 return errors.Annotatef(err, "error fetching internal address for machine %q", machine) 40 41 } 42 43 publicAddress, err := machine.PublicAddress() 44 if err != nil { 45 return errors.Annotatef(err, "error fetching public address for machine %q", machine) 46 47 } 48 49 info := a.backend.RestoreInfo() 50 // Signal to current state and api server that restore will begin 51 err = info.SetStatus(state.RestoreInProgress) 52 if err != nil { 53 return errors.Annotatef(err, "cannot set the server to %q mode", state.RestoreInProgress) 54 } 55 // Any abnormal termination of this function will mark restore as failed, 56 // succesful termination will call Exit and never run this. 57 defer info.SetStatus(state.RestoreFailed) 58 59 instanceId, err := machine.InstanceId() 60 if err != nil { 61 return errors.Annotate(err, "cannot obtain instance id for machine to be restored") 62 } 63 64 logger.Infof("beginning server side restore of backup %q", p.BackupId) 65 // Restore 66 restoreArgs := backups.RestoreArgs{ 67 PrivateAddress: addr.Value, 68 PublicAddress: publicAddress.Value, 69 NewInstId: instanceId, 70 NewInstTag: machine.Tag(), 71 NewInstSeries: machine.Series(), 72 } 73 74 session := a.backend.MongoSession().Copy() 75 defer session.Close() 76 77 // Don't go if HA isn't ready. 78 err = waitUntilReady(session, 60) 79 if err != nil { 80 return errors.Annotatef(err, "HA not ready; try again later") 81 } 82 83 mgoInfo := a.backend.MongoConnectionInfo() 84 logger.Debugf("mongo info from state %+v", mgoInfo) 85 v, err := a.backend.MongoVersion() 86 if err != nil { 87 return errors.Annotatef(err, "discovering mongo version") 88 } 89 mongoVersion, err := mongo.NewVersion(v) 90 if err != nil { 91 return errors.Trace(err) 92 } 93 94 dbInfo, err := backups.NewDBInfo(mgoInfo, session, mongoVersion) 95 if err != nil { 96 return errors.Trace(err) 97 } 98 99 oldTagString, err := backup.Restore(p.BackupId, dbInfo, restoreArgs) 100 if err != nil { 101 return errors.Annotate(err, "restore failed") 102 } 103 104 // A backup can be made of any component of an ha array. 105 // The files in a backup don't contain purely relativized paths. 106 // If the backup is made of the bootstrap node (machine 0) the 107 // recently created machine will have the same paths and therefore 108 // the startup scripts will fit the new juju. If the backup belongs 109 // to a different machine, we need to create a new set of startup 110 // scripts and exit with 0 (so that the current script does not try 111 // to restart the old juju, which will no longer be there). 112 if oldTagString != nil && oldTagString != bootstrapNode { 113 srvName := fmt.Sprintf("jujud-%s", oldTagString) 114 srv, err := service.DiscoverService(srvName, common.Conf{}) 115 if err != nil { 116 return errors.Annotatef(err, "cannot find %q service", srvName) 117 } 118 if err := srv.Start(); err != nil { 119 return errors.Annotatef(err, "cannot start %q service", srvName) 120 } 121 // We dont want machine-0 to restart since the new one has a different tag. 122 // We started the new one above. 123 os.Exit(0) 124 } 125 126 // After restoring, the api server needs a forced restart, tomb will not work 127 // this is because we change all of juju configuration files and mongo too. 128 // Exiting with 0 would prevent upstart to respawn the process 129 130 // NOTE(fwereade): the apiserver needs to be restarted, yes, but 131 // this approach is completely broken. The only place it's ever 132 // ok to use os.Exit is in a main() func that's *so* simple as to 133 // be reasonably left untested. 134 // 135 // And passing os.Exit in wouldn't make this any better either, 136 // just using it subverts the expectations of *everything* else 137 // running in the process. 138 os.Exit(1) 139 return nil 140 } 141 142 // PrepareRestore implements the server side of Backups.PrepareRestore. 143 func (a *API) PrepareRestore() error { 144 info := a.backend.RestoreInfo() 145 logger.Infof("entering restore preparation mode") 146 return info.SetStatus(state.RestorePending) 147 } 148 149 // FinishRestore implements the server side of Backups.FinishRestore. 150 func (a *API) FinishRestore() error { 151 info := a.backend.RestoreInfo() 152 currentStatus, err := info.Status() 153 if err != nil { 154 return errors.Trace(err) 155 } 156 if currentStatus != state.RestoreFinished { 157 if err := info.SetStatus(state.RestoreFailed); err != nil { 158 return errors.Trace(err) 159 } 160 return errors.Errorf("Restore did not finish succesfuly") 161 } 162 logger.Infof("Succesfully restored") 163 return info.SetStatus(state.RestoreChecked) 164 }