github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"crypto/rand"
     8  	"fmt"
     9  	"io"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils"
    16  	"launchpad.net/gnuflag"
    17  
    18  	"github.com/juju/juju/api/backups"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/bootstrap"
    24  	"github.com/juju/juju/environs/config"
    25  )
    26  
    27  // NewRestoreCommand returns a command used to restore a backup.
    28  func NewRestoreCommand() cmd.Command {
    29  	restoreCmd := &restoreCommand{}
    30  	restoreCmd.getEnvironFunc = restoreCmd.getEnviron
    31  	restoreCmd.newAPIClientFunc = func() (RestoreAPI, error) {
    32  		return restoreCmd.newClient()
    33  	}
    34  	restoreCmd.getArchiveFunc = getArchive
    35  	return modelcmd.Wrap(restoreCmd)
    36  }
    37  
    38  // restoreCommand is a subcommand of backups that implement the restore behavior
    39  // it is invoked with "juju restore-backup".
    40  type restoreCommand struct {
    41  	CommandBase
    42  	constraints constraints.Value
    43  	filename    string
    44  	backupId    string
    45  	bootstrap   bool
    46  	uploadTools bool
    47  
    48  	newAPIClientFunc func() (RestoreAPI, error)
    49  	getEnvironFunc   func(string, *params.BackupsMetadataResult) (environs.Environ, error)
    50  	getArchiveFunc   func(string) (ArchiveReader, *params.BackupsMetadataResult, error)
    51  }
    52  
    53  // RestoreAPI is used to invoke various API calls.
    54  type RestoreAPI interface {
    55  	// Close is taken from io.Closer.
    56  	Close() error
    57  
    58  	// Restore is taken from backups.Client.
    59  	Restore(backupId string, newClient backups.ClientConnection) error
    60  
    61  	// RestoreReader is taken from backups.Client.
    62  	RestoreReader(r io.ReadSeeker, meta *params.BackupsMetadataResult, newClient backups.ClientConnection) error
    63  }
    64  
    65  var restoreDoc = `
    66  Restores a backup that was previously created with "juju create-backup".
    67  
    68  This command creates a new controller and arranges for it to replace
    69  the previous controller for a model.  It does *not* restore
    70  an existing server to a previous state, but instead creates a new server
    71  with equivalent state.  As part of restore, all known instances are
    72  configured to treat the new controller as their master.
    73  
    74  The given constraints will be used to choose the new instance.
    75  
    76  If the provided state cannot be restored, this command will fail with
    77  an appropriate message.  For instance, if the existing bootstrap
    78  instance is already running then the command will fail with a message
    79  to that effect.
    80  `
    81  
    82  var BootstrapFunc = bootstrap.Bootstrap
    83  
    84  // Info returns the content for --help.
    85  func (c *restoreCommand) Info() *cmd.Info {
    86  	return &cmd.Info{
    87  		Name:    "restore-backup",
    88  		Purpose: "restore from a backup archive to a new controller",
    89  		Args:    "",
    90  		Doc:     strings.TrimSpace(restoreDoc),
    91  	}
    92  }
    93  
    94  // SetFlags handles known option flags.
    95  func (c *restoreCommand) SetFlags(f *gnuflag.FlagSet) {
    96  	c.CommandBase.SetFlags(f)
    97  	f.Var(constraints.ConstraintsValue{Target: &c.constraints},
    98  		"constraints", "set model constraints")
    99  
   100  	f.BoolVar(&c.bootstrap, "b", false, "bootstrap a new state machine")
   101  	f.StringVar(&c.filename, "file", "", "provide a file to be used as the backup.")
   102  	f.StringVar(&c.backupId, "id", "", "provide the name of the backup to be restored.")
   103  	f.BoolVar(&c.uploadTools, "upload-tools", false, "upload tools if bootstraping a new machine.")
   104  }
   105  
   106  // Init is where the preconditions for this commands can be checked.
   107  func (c *restoreCommand) Init(args []string) error {
   108  	if c.filename == "" && c.backupId == "" {
   109  		return errors.Errorf("you must specify either a file or a backup id.")
   110  	}
   111  	if c.filename != "" && c.backupId != "" {
   112  		return errors.Errorf("you must specify either a file or a backup id but not both.")
   113  	}
   114  	if c.backupId != "" && c.bootstrap {
   115  		return errors.Errorf("it is not possible to rebootstrap and restore from an id.")
   116  	}
   117  	var err error
   118  	if c.filename != "" {
   119  		c.filename, err = filepath.Abs(c.filename)
   120  		if err != nil {
   121  			return errors.Trace(err)
   122  		}
   123  	}
   124  	return nil
   125  }
   126  
   127  // getEnviron returns the environ for the specified controller, or
   128  // mocked out environ for testing.
   129  func (c *restoreCommand) getEnviron(controllerName string, meta *params.BackupsMetadataResult) (environs.Environ, error) {
   130  	// TODO(axw) delete this and -b in 2.0-beta2. We will update bootstrap
   131  	// with a flag to specify a restore file. When we do that, we'll need
   132  	// to extract the CA cert from the backup, and we'll need to reset the
   133  	// password after restore so the admin user can login.
   134  	// We also need to store things like the admin-secret, controller
   135  	// certificate etc with the backup.
   136  	store := c.ClientStore()
   137  	cfg, err := modelcmd.NewGetBootstrapConfigFunc(store)(controllerName)
   138  	if err != nil {
   139  		return nil, errors.Annotate(err, "cannot restore from a machine other than the one used to bootstrap")
   140  	}
   141  
   142  	// Reset current model to admin so first bootstrap succeeds.
   143  	err = store.SetCurrentModel(controllerName, environs.AdminUser, "admin")
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  
   148  	// Get the local admin user so we can use the password as the admin secret.
   149  	var adminSecret string
   150  	account, err := store.AccountByName(controllerName, environs.AdminUser)
   151  	if err == nil {
   152  		adminSecret = account.Password
   153  	} else if errors.IsNotFound(err) {
   154  		// No relevant local admin user so generate a new secret.
   155  		buf := make([]byte, 16)
   156  		if _, err := io.ReadFull(rand.Reader, buf); err != nil {
   157  			return nil, errors.Annotate(err, "generating new admin secret")
   158  		}
   159  		adminSecret = fmt.Sprintf("%x", buf)
   160  	} else {
   161  		return nil, errors.Trace(err)
   162  	}
   163  
   164  	// Turn on safe mode so that the newly bootstrapped instance
   165  	// will not destroy all the instances it does not know about.
   166  	// Also set the admin secret and ca cert info.
   167  	cfg, err = cfg.Apply(map[string]interface{}{
   168  		"provisioner-safe-mode": true,
   169  		"admin-secret":          adminSecret,
   170  		"ca-private-key":        meta.CAPrivateKey,
   171  		"ca-cert":               meta.CACert,
   172  	})
   173  	if err != nil {
   174  		return nil, errors.Annotatef(err, "cannot enable provisioner-safe-mode")
   175  	}
   176  	return environs.New(cfg)
   177  }
   178  
   179  // rebootstrap will bootstrap a new server in safe-mode (not killing any other agent)
   180  // if there is no current server available to restore to.
   181  func (c *restoreCommand) rebootstrap(ctx *cmd.Context, meta *params.BackupsMetadataResult) error {
   182  	env, err := c.getEnvironFunc(c.ControllerName(), meta)
   183  	if err != nil {
   184  		return errors.Trace(err)
   185  	}
   186  	instanceIds, err := env.ControllerInstances()
   187  	if err != nil && errors.Cause(err) != environs.ErrNotBootstrapped {
   188  		return errors.Annotatef(err, "cannot determine controller instances")
   189  	}
   190  	if len(instanceIds) > 0 {
   191  		inst, err := env.Instances(instanceIds)
   192  		if err == nil {
   193  			return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst)
   194  		}
   195  		if err != environs.ErrNoInstances {
   196  			return errors.Annotatef(err, "cannot detect whether old instance is still running")
   197  		}
   198  	}
   199  
   200  	// We require a hosted model config to bootstrap. We'll fill in some defaults
   201  	// just to get going. The restore will clear the initial state.
   202  	hostedModelUUID, err := utils.NewUUID()
   203  	if err != nil {
   204  		return errors.Trace(err)
   205  	}
   206  	hostedModelConfig := map[string]interface{}{
   207  		"name":         "default",
   208  		config.UUIDKey: hostedModelUUID.String(),
   209  	}
   210  
   211  	args := bootstrap.BootstrapParams{
   212  		ModelConstraints:  c.constraints,
   213  		UploadTools:       c.uploadTools,
   214  		HostedModelConfig: hostedModelConfig,
   215  	}
   216  	if err := BootstrapFunc(modelcmd.BootstrapContext(ctx), env, args); err != nil {
   217  		return errors.Annotatef(err, "cannot bootstrap new instance")
   218  	}
   219  	return nil
   220  }
   221  
   222  func (c *restoreCommand) newClient() (*backups.Client, error) {
   223  	client, err := c.NewAPIClient()
   224  	if err != nil {
   225  		return nil, errors.Trace(err)
   226  	}
   227  	backupsClient, ok := client.(*backups.Client)
   228  	if !ok {
   229  		return nil, errors.Errorf("invalid client for backups")
   230  	}
   231  	return backupsClient, nil
   232  }
   233  
   234  // Run is the entry point for this command.
   235  func (c *restoreCommand) Run(ctx *cmd.Context) error {
   236  	if c.Log != nil {
   237  		if err := c.Log.Start(ctx); err != nil {
   238  			return err
   239  		}
   240  	}
   241  
   242  	var archive ArchiveReader
   243  	var meta *params.BackupsMetadataResult
   244  	target := c.backupId
   245  	if c.filename != "" {
   246  		// Read archive specified by the filename;
   247  		// we'll need the info later regardless if
   248  		// we need it now to rebootstrap.
   249  		target = c.filename
   250  		var err error
   251  		archive, meta, err = c.getArchiveFunc(c.filename)
   252  		if err != nil {
   253  			return errors.Trace(err)
   254  		}
   255  		defer archive.Close()
   256  
   257  		if c.bootstrap {
   258  			if err := c.rebootstrap(ctx, meta); err != nil {
   259  				return errors.Trace(err)
   260  			}
   261  		}
   262  	}
   263  
   264  	client, err := c.newAPIClientFunc()
   265  	if err != nil {
   266  		return errors.Trace(err)
   267  	}
   268  	defer client.Close()
   269  
   270  	// We have a backup client, now use the relevant method
   271  	// to restore the backup.
   272  	if c.filename != "" {
   273  		err = client.RestoreReader(archive, meta, c.newClient)
   274  	} else {
   275  		err = client.Restore(c.backupId, c.newClient)
   276  	}
   277  	if err != nil {
   278  		return nil
   279  	}
   280  	fmt.Fprintf(ctx.Stdout, "restore from %q completed\n", target)
   281  	return nil
   282  }