github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/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  	"fmt"
     8  	"os"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/mongo"
    15  	"github.com/juju/juju/service"
    16  	"github.com/juju/juju/service/common"
    17  	"github.com/juju/juju/state"
    18  	"github.com/juju/juju/state/backups"
    19  )
    20  
    21  var bootstrapNode = names.NewMachineTag("0")
    22  
    23  // Restore implements the server side of Backups.Restore.
    24  func (a *API) Restore(p params.RestoreArgs) error {
    25  	logger.Infof("Starting server side restore")
    26  
    27  	// Get hold of a backup file Reader
    28  	backup, closer := newBackups(a.backend)
    29  	defer closer.Close()
    30  
    31  	// Obtain the address of current machine, where we will be performing restore.
    32  	machine, err := a.backend.Machine(a.machineID)
    33  	if err != nil {
    34  		return errors.Trace(err)
    35  	}
    36  
    37  	addr, err := machine.PrivateAddress()
    38  	if err != nil {
    39  		return errors.Annotatef(err, "error fetching internal address for machine %q", machine)
    40  
    41  	}
    42  
    43  	publicAddress, err := machine.PublicAddress()
    44  	if err != nil {
    45  		return errors.Annotatef(err, "error fetching public address for machine %q", machine)
    46  
    47  	}
    48  
    49  	info := a.backend.RestoreInfo()
    50  	// Signal to current state and api server that restore will begin
    51  	err = info.SetStatus(state.RestoreInProgress)
    52  	if err != nil {
    53  		return errors.Annotatef(err, "cannot set the server to %q mode", state.RestoreInProgress)
    54  	}
    55  	// Any abnormal termination of this function will mark restore as failed,
    56  	// succesful termination will call Exit and never run this.
    57  	defer info.SetStatus(state.RestoreFailed)
    58  
    59  	instanceId, err := machine.InstanceId()
    60  	if err != nil {
    61  		return errors.Annotate(err, "cannot obtain instance id for machine to be restored")
    62  	}
    63  
    64  	logger.Infof("beginning server side restore of backup %q", p.BackupId)
    65  	// Restore
    66  	restoreArgs := backups.RestoreArgs{
    67  		PrivateAddress: addr.Value,
    68  		PublicAddress:  publicAddress.Value,
    69  		NewInstId:      instanceId,
    70  		NewInstTag:     machine.Tag(),
    71  		NewInstSeries:  machine.Series(),
    72  	}
    73  
    74  	session := a.backend.MongoSession().Copy()
    75  	defer session.Close()
    76  
    77  	// Don't go if HA isn't ready.
    78  	err = waitUntilReady(session, 60)
    79  	if err != nil {
    80  		return errors.Annotatef(err, "HA not ready; try again later")
    81  	}
    82  
    83  	mgoInfo := a.backend.MongoConnectionInfo()
    84  	logger.Debugf("mongo info from state %+v", mgoInfo)
    85  	v, err := a.backend.MongoVersion()
    86  	if err != nil {
    87  		return errors.Annotatef(err, "discovering mongo version")
    88  	}
    89  	mongoVersion, err := mongo.NewVersion(v)
    90  	if err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  
    94  	dbInfo, err := backups.NewDBInfo(mgoInfo, session, mongoVersion)
    95  	if err != nil {
    96  		return errors.Trace(err)
    97  	}
    98  
    99  	oldTagString, err := backup.Restore(p.BackupId, dbInfo, restoreArgs)
   100  	if err != nil {
   101  		return errors.Annotate(err, "restore failed")
   102  	}
   103  
   104  	// A backup can be made of any component of an ha array.
   105  	// The files in a backup don't contain purely relativized paths.
   106  	// If the backup is made of the bootstrap node (machine 0) the
   107  	// recently created machine will have the same paths and therefore
   108  	// the startup scripts will fit the new juju. If the backup belongs
   109  	// to a different machine, we need to create a new set of startup
   110  	// scripts and exit with 0 (so that the current script does not try
   111  	// to restart the old juju, which will no longer be there).
   112  	if oldTagString != nil && oldTagString != bootstrapNode {
   113  		srvName := fmt.Sprintf("jujud-%s", oldTagString)
   114  		srv, err := service.DiscoverService(srvName, common.Conf{})
   115  		if err != nil {
   116  			return errors.Annotatef(err, "cannot find %q service", srvName)
   117  		}
   118  		if err := srv.Start(); err != nil {
   119  			return errors.Annotatef(err, "cannot start %q service", srvName)
   120  		}
   121  		// We dont want machine-0 to restart since the new one has a different tag.
   122  		// We started the new one above.
   123  		os.Exit(0)
   124  	}
   125  
   126  	// After restoring, the api server needs a forced restart, tomb will not work
   127  	// this is because we change all of juju configuration files and mongo too.
   128  	// Exiting with 0 would prevent upstart to respawn the process
   129  
   130  	// NOTE(fwereade): the apiserver needs to be restarted, yes, but
   131  	// this approach is completely broken. The only place it's ever
   132  	// ok to use os.Exit is in a main() func that's *so* simple as to
   133  	// be reasonably left untested.
   134  	//
   135  	// And passing os.Exit in wouldn't make this any better either,
   136  	// just using it subverts the expectations of *everything* else
   137  	// running in the process.
   138  	os.Exit(1)
   139  	return nil
   140  }
   141  
   142  // PrepareRestore implements the server side of Backups.PrepareRestore.
   143  func (a *API) PrepareRestore() error {
   144  	info := a.backend.RestoreInfo()
   145  	logger.Infof("entering restore preparation mode")
   146  	return info.SetStatus(state.RestorePending)
   147  }
   148  
   149  // FinishRestore implements the server side of Backups.FinishRestore.
   150  func (a *API) FinishRestore() error {
   151  	info := a.backend.RestoreInfo()
   152  	currentStatus, err := info.Status()
   153  	if err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	if currentStatus != state.RestoreFinished {
   157  		if err := info.SetStatus(state.RestoreFailed); err != nil {
   158  			return errors.Trace(err)
   159  		}
   160  		return errors.Errorf("Restore did not finish succesfuly")
   161  	}
   162  	logger.Infof("Succesfully restored")
   163  	return info.SetStatus(state.RestoreChecked)
   164  }