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