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