github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 results, err := c.List() 78 if err != nil { 79 return errors.Trace(err) 80 } 81 82 // Do not upload if backup already exists on controller. 83 list := results.List 84 for _, b := range list { 85 if b.Checksum == meta.Checksum { 86 return c.restore(b.ID, newClient) 87 } 88 } 89 90 // Upload. 91 backupId, err := c.Upload(r, *meta) 92 if err != nil { 93 finishErr := finishRestore(newClient) 94 logger.Errorf("could not clean up after failed backup upload: %v", finishErr) 95 return errors.Annotatef(err, "cannot upload backup file") 96 } 97 98 return c.restore(backupId, newClient) 99 } 100 101 // Restore performs restore using a backup id corresponding to a backup stored in the server. 102 func (c *Client) Restore(backupId string, newClient ClientConnection) error { 103 if err := prepareRestore(newClient); err != nil { 104 return errors.Trace(err) 105 } 106 logger.Debugf("Server in 'about to restore' mode") 107 return c.restore(backupId, newClient) 108 } 109 110 func restoreAttempt(client *Client, restoreArgs params.RestoreArgs) (error, error) { 111 var remoteError error 112 defer client.Close() 113 err := client.facade.FacadeCall("Restore", restoreArgs, &remoteError) 114 return err, remoteError 115 } 116 117 // restore is responsible for triggering the whole restore process in a remote 118 // machine. The backup information for the process should already be in the 119 // server and loaded in the backup storage under the backupId id. 120 // It takes backupId as the identifier for the remote backup file and a 121 // client connection factory newClient (newClient should no longer be 122 // necessary when lp:1399722 is sorted out). 123 func (c *Client) restore(backupId string, newClient ClientConnection) error { 124 var err, remoteError error 125 126 // Restore 127 restoreArgs := params.RestoreArgs{ 128 BackupId: backupId, 129 } 130 131 cleanExit := false 132 for a := restoreStrategy.Start(); a.Next(); { 133 logger.Debugf("Attempting Restore of %q", backupId) 134 var restoreClient *Client 135 restoreClient, err = newClient() 136 if err != nil { 137 return errors.Trace(err) 138 } 139 140 err, remoteError = restoreAttempt(restoreClient, restoreArgs) 141 142 // A ShutdownErr signals that Restore almost certainly finished and 143 // triggered Exit. 144 if (err == nil || rpc.IsShutdownErr(err)) && remoteError == nil { 145 cleanExit = true 146 break 147 } 148 if !params.IsCodeUpgradeInProgress(err) || remoteError != nil { 149 finishErr := finishRestore(newClient) 150 logger.Errorf("could not clean up after failed restore attempt: %v", finishErr) 151 return errors.Annotatef(err, "cannot perform restore: %v", remoteError) 152 } 153 } 154 if !cleanExit { 155 finishErr := finishRestore(newClient) 156 if finishErr != nil { 157 logger.Errorf("could not clean up failed restore: %v", finishErr) 158 } 159 return errors.Annotatef(err, "cannot perform restore: %v", remoteError) 160 } 161 162 err = finishRestore(newClient) 163 if err != nil { 164 return errors.Annotatef(err, "could not finish restore process: %v", remoteError) 165 } 166 return nil 167 } 168 169 func finishAttempt(client *Client) (error, error) { 170 var remoteError error 171 defer client.Close() 172 err := client.facade.FacadeCall("FinishRestore", nil, &remoteError) 173 return err, remoteError 174 } 175 176 // finishRestore since Restore call will end up with a reset 177 // controller, finish restore will check that the the newly 178 // placed controller has the mark of restore complete. 179 // upstart should have restarted the api server so we reconnect. 180 func finishRestore(newClient ClientConnection) error { 181 var err, remoteError error 182 for a := restoreStrategy.Start(); a.Next(); { 183 logger.Debugf("Attempting finishRestore") 184 var finishClient *Client 185 finishClient, err = newClient() 186 if err != nil { 187 return errors.Trace(err) 188 } 189 190 err, remoteError = finishAttempt(finishClient) 191 if err == nil && remoteError == nil { 192 return nil 193 } 194 195 if !params.IsCodeUpgradeInProgress(err) || remoteError != nil { 196 return errors.Annotatef(err, "cannot complete restore: %v", remoteError) 197 } 198 } 199 return errors.Annotatef(err, "cannot complete restore: %v", remoteError) 200 }