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  }