github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/api/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 "io" 8 "reflect" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils" 13 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/rpc" 16 ) 17 18 // TODO: There are no unit tests for this file. 19 // lp1545568 opened to track their addition. 20 21 var ( 22 // restoreStrategy is the attempt strategy for api server calls re-attempts in case 23 // the server is upgrading. 24 restoreStrategy = utils.AttemptStrategy{ 25 Delay: 10 * time.Second, 26 Min: 10, 27 } 28 ) 29 30 // isUpgradeInProgressErr returns whether or not the error 31 // is an "upgrade in progress" error. This is necessary as 32 // the error type returned from a facade call is rpc.RequestError 33 // and we cannot use params.IsCodeUpgradeInProgress 34 func isUpgradeInProgressErr(err error) bool { 35 return reflect.DeepEqual(errors.Cause(err), &rpc.RequestError{Message: params.CodeUpgradeInProgress, Code: params.CodeUpgradeInProgress}) 36 } 37 38 // ClientConnection type represents a function capable of spawning a new Client connection 39 // it is used to pass around connection factories when necessary. 40 // TODO(perrito666) This is a workaround for lp:1399722 . 41 type ClientConnection func() (*Client, error) 42 43 // closerfunc is a function that allows you to close a client connection. 44 type closerFunc func() error 45 46 func prepareAttempt(client *Client) (error, error) { 47 var remoteError error 48 defer client.Close() 49 err := client.facade.FacadeCall("PrepareRestore", nil, &remoteError) 50 return err, remoteError 51 } 52 53 func prepareRestore(newClient ClientConnection) error { 54 var err, remoteError error 55 56 // PrepareRestore puts the server into a state that only allows 57 // for restore to be called. This is to avoid the data loss if 58 // users try to perform actions that are going to be overwritten 59 // by restore. 60 for a := restoreStrategy.Start(); a.Next(); { 61 logger.Debugf("Will attempt to call 'PrepareRestore'") 62 client, clientErr := newClient() 63 if clientErr != nil { 64 return errors.Trace(clientErr) 65 } 66 err, remoteError = prepareAttempt(client) 67 if err == nil && remoteError == nil { 68 return nil 69 } 70 if !isUpgradeInProgressErr(err) || remoteError != nil { 71 return errors.Annotatef(err, "could not start prepare restore mode, server returned: %v", remoteError) 72 } 73 } 74 return errors.Annotatef(err, "could not start restore process: %v", remoteError) 75 } 76 77 // RestoreReader restores the contents of backupFile as backup. 78 func (c *Client) RestoreReader(r io.ReadSeeker, meta *params.BackupsMetadataResult, newClient ClientConnection) error { 79 if err := prepareRestore(newClient); err != nil { 80 return errors.Trace(err) 81 } 82 logger.Debugf("Server is now in 'about to restore' mode, proceeding to upload the backup file") 83 84 // Upload. 85 backupId, err := c.Upload(r, *meta) 86 if err != nil { 87 finishErr := finishRestore(newClient) 88 logger.Errorf("could not exit restoring status: %v", finishErr) 89 return errors.Annotatef(err, "cannot upload backup file") 90 } 91 return c.restore(backupId, newClient) 92 } 93 94 // Restore performs restore using a backup id corresponding to a backup stored in the server. 95 func (c *Client) Restore(backupId string, newClient ClientConnection) error { 96 if err := prepareRestore(newClient); err != nil { 97 return errors.Trace(err) 98 } 99 logger.Debugf("Server in 'about to restore' mode") 100 return c.restore(backupId, newClient) 101 } 102 103 func restoreAttempt(client *Client, restoreArgs params.RestoreArgs) (error, error) { 104 var remoteError error 105 defer client.Close() 106 err := client.facade.FacadeCall("Restore", restoreArgs, &remoteError) 107 return err, remoteError 108 } 109 110 // restore is responsible for triggering the whole restore process in a remote 111 // machine. The backup information for the process should already be in the 112 // server and loaded in the backup storage under the backupId id. 113 // It takes backupId as the identifier for the remote backup file and a 114 // client connection factory newClient (newClient should no longer be 115 // necessary when lp:1399722 is sorted out). 116 func (c *Client) restore(backupId string, newClient ClientConnection) error { 117 var err, remoteError error 118 119 // Restore 120 restoreArgs := params.RestoreArgs{ 121 BackupId: backupId, 122 } 123 124 cleanExit := false 125 for a := restoreStrategy.Start(); a.Next(); { 126 logger.Debugf("Attempting Restore of %q", backupId) 127 var restoreClient *Client 128 restoreClient, err = newClient() 129 if err != nil { 130 return errors.Trace(err) 131 } 132 133 err, remoteError = restoreAttempt(restoreClient, restoreArgs) 134 135 // A ShutdownErr signals that Restore almost certainly finished and 136 // triggered Exit. 137 if (err == nil || rpc.IsShutdownErr(err)) && remoteError == nil { 138 cleanExit = true 139 break 140 } 141 if remoteError != nil || !isUpgradeInProgressErr(err) { 142 finishErr := finishRestore(newClient) 143 logger.Errorf("could not exit restoring status: %v", finishErr) 144 return errors.Annotatef(err, "cannot perform restore: %v", remoteError) 145 } 146 } 147 if !cleanExit { 148 finishErr := finishRestore(newClient) 149 if finishErr != nil { 150 logger.Errorf("could not exit restoring status: %v", finishErr) 151 } 152 return errors.Annotatef(err, "cannot perform restore: %v", remoteError) 153 } 154 155 err = finishRestore(newClient) 156 if err != nil { 157 return errors.Annotatef(err, "could not finish restore process: %v", remoteError) 158 } 159 return nil 160 } 161 162 func finishAttempt(client *Client) (error, error) { 163 var remoteError error 164 defer client.Close() 165 err := client.facade.FacadeCall("FinishRestore", nil, &remoteError) 166 return err, remoteError 167 } 168 169 // finishRestore since Restore call will end up with a reset 170 // controller, finish restore will check that the the newly 171 // placed controller has the mark of restore complete. 172 // upstart should have restarted the api server so we reconnect. 173 func finishRestore(newClient ClientConnection) error { 174 var err, remoteError error 175 for a := restoreStrategy.Start(); a.Next(); { 176 logger.Debugf("Attempting finishRestore") 177 var finishClient *Client 178 finishClient, err = newClient() 179 if err != nil { 180 return errors.Trace(err) 181 } 182 183 err, remoteError = finishAttempt(finishClient) 184 if err == nil && remoteError == nil { 185 return nil 186 } 187 188 if !isUpgradeInProgressErr(err) || remoteError != nil { 189 return errors.Annotatef(err, "cannot complete restore: %v", remoteError) 190 } 191 } 192 return errors.Annotatef(err, "cannot complete restore: %v", remoteError) 193 }