github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cmd/juju/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  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/api/backups"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/cmd/envcmd"
    18  	"github.com/juju/juju/constraints"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/environs/bootstrap"
    21  	"github.com/juju/juju/environs/configstore"
    22  )
    23  
    24  // RestoreCommand is a subcommand of backups that implement the restore behaior
    25  // it is invoked with "juju backups restore".
    26  type RestoreCommand struct {
    27  	CommandBase
    28  	constraints constraints.Value
    29  	filename    string
    30  	backupId    string
    31  	bootstrap   bool
    32  }
    33  
    34  var restoreDoc = `
    35  Restores a backup that was previously created with "juju backup" and
    36  "juju backups create".
    37  
    38  This command creates a new state server and arranges for it to replace
    39  the previous state server for an environment.  It does *not* restore
    40  an existing server to a previous state, but instead creates a new server
    41  with equivalent state.  As part of restore, all known instances are
    42  configured to treat the new state server as their master.
    43  
    44  The given constraints will be used to choose the new instance.
    45  
    46  If the provided state cannot be restored, this command will fail with
    47  an appropriate message.  For instance, if the existing bootstrap
    48  instance is already running then the command will fail with a message
    49  to that effect.
    50  `
    51  
    52  // Info returns the content for --help.
    53  func (c *RestoreCommand) Info() *cmd.Info {
    54  	return &cmd.Info{
    55  		Name:    "restore",
    56  		Purpose: "restore from a backup archive to a new state server",
    57  		Args:    "",
    58  		Doc:     strings.TrimSpace(restoreDoc),
    59  	}
    60  }
    61  
    62  // SetFlags handles known option flags.
    63  func (c *RestoreCommand) SetFlags(f *gnuflag.FlagSet) {
    64  	f.Var(constraints.ConstraintsValue{Target: &c.constraints},
    65  		"constraints", "set environment constraints")
    66  
    67  	f.BoolVar(&c.bootstrap, "b", false, "bootstrap a new state machine")
    68  	f.StringVar(&c.filename, "file", "", "provide a file to be used as the backup.")
    69  	f.StringVar(&c.backupId, "id", "", "provide the name of the backup to be restored.")
    70  }
    71  
    72  // Init is where the preconditions for this commands can be checked.
    73  func (c *RestoreCommand) Init(args []string) error {
    74  	if c.filename == "" && c.backupId == "" {
    75  		return errors.Errorf("you must specify either a file or a backup id.")
    76  	}
    77  	if c.filename != "" && c.backupId != "" {
    78  		return errors.Errorf("you must specify either a file or a backup id but not both.")
    79  	}
    80  	if c.backupId != "" && c.bootstrap {
    81  		return errors.Errorf("it is not possible to rebootstrap and restore from an id.")
    82  	}
    83  	var err error
    84  	if c.filename != "" {
    85  		c.filename, err = filepath.Abs(c.filename)
    86  		if err != nil {
    87  			return errors.Trace(err)
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  const restoreAPIIncompatibility = "server version not compatible for " +
    94  	"restore with client version"
    95  
    96  // runRestore will implement the actual calls to the different Client parts
    97  // of restore.
    98  func (c *RestoreCommand) runRestore(ctx *cmd.Context) error {
    99  	client, closer, err := c.newClient()
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	defer closer()
   104  	var target string
   105  	var rErr error
   106  	if c.filename != "" {
   107  		target = c.filename
   108  		archive, meta, err := getArchive(c.filename)
   109  		if err != nil {
   110  			return errors.Trace(err)
   111  		}
   112  		defer archive.Close()
   113  
   114  		rErr = client.RestoreReader(archive, meta, c.newClient)
   115  	} else {
   116  		target = c.backupId
   117  		rErr = client.Restore(c.backupId, c.newClient)
   118  	}
   119  	if params.IsCodeNotImplemented(rErr) {
   120  		return errors.Errorf(restoreAPIIncompatibility)
   121  	}
   122  	if rErr != nil {
   123  		return errors.Trace(rErr)
   124  	}
   125  
   126  	fmt.Fprintf(ctx.Stdout, "restore from %q completed\n", target)
   127  	return nil
   128  }
   129  
   130  // rebootstrap will bootstrap a new server in safe-mode (not killing any other agent)
   131  // if there is no current server available to restore to.
   132  func (c *RestoreCommand) rebootstrap(ctx *cmd.Context) error {
   133  	store, err := configstore.Default()
   134  	if err != nil {
   135  		return errors.Trace(err)
   136  	}
   137  	cfg, err := c.Config(store, nil)
   138  	if err != nil {
   139  		return errors.Trace(err)
   140  	}
   141  	// Turn on safe mode so that the newly bootstrapped instance
   142  	// will not destroy all the instances it does not know about.
   143  	cfg, err = cfg.Apply(map[string]interface{}{
   144  		"provisioner-safe-mode": true,
   145  	})
   146  	if err != nil {
   147  		return errors.Annotatef(err, "cannot enable provisioner-safe-mode")
   148  	}
   149  	env, err := environs.New(cfg)
   150  	if err != nil {
   151  		return errors.Trace(err)
   152  	}
   153  	instanceIds, err := env.StateServerInstances()
   154  	if err != nil {
   155  		return errors.Annotatef(err, "cannot determine state server instances")
   156  	}
   157  	if len(instanceIds) == 0 {
   158  		return errors.Errorf("no instances found; perhaps the environment was not bootstrapped")
   159  	}
   160  	inst, err := env.Instances(instanceIds)
   161  	if err == nil {
   162  		return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst)
   163  	}
   164  	if err != environs.ErrNoInstances {
   165  		return errors.Annotatef(err, "cannot detect whether old instance is still running")
   166  	}
   167  
   168  	cons := c.constraints
   169  	args := bootstrap.BootstrapParams{Constraints: cons}
   170  	if err := bootstrap.Bootstrap(envcmd.BootstrapContext(ctx), env, args); err != nil {
   171  		return errors.Annotatef(err, "cannot bootstrap new instance")
   172  	}
   173  	return nil
   174  }
   175  
   176  func (c *RestoreCommand) newClient() (*backups.Client, func() error, error) {
   177  	client, err := c.NewAPIClient()
   178  	if err != nil {
   179  		return nil, nil, errors.Trace(err)
   180  	}
   181  	backupsClient, ok := client.(*backups.Client)
   182  	if !ok {
   183  		return nil, nil, errors.Errorf("invalid client for backups")
   184  	}
   185  	return backupsClient, client.Close, nil
   186  }
   187  
   188  // Run is the entry point for this command.
   189  func (c *RestoreCommand) Run(ctx *cmd.Context) error {
   190  	if c.bootstrap {
   191  		if err := c.rebootstrap(ctx); err != nil {
   192  			return errors.Trace(err)
   193  		}
   194  	}
   195  	return c.runRestore(ctx)
   196  }