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  }