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