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  }