github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/commands/migrate.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/errors"
     9  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    10  	"gopkg.in/macaroon.v2-unstable"
    11  
    12  	"github.com/juju/juju/api"
    13  	"github.com/juju/juju/api/controller"
    14  	jujucmd "github.com/juju/juju/cmd"
    15  	"github.com/juju/juju/cmd/modelcmd"
    16  	"github.com/juju/juju/jujuclient"
    17  )
    18  
    19  func newMigrateCommand() modelcmd.ModelCommand {
    20  	var cmd migrateCommand
    21  	cmd.newAPIRoot = cmd.CommandBase.NewAPIRoot
    22  	return modelcmd.Wrap(&cmd, modelcmd.WrapSkipModelFlags)
    23  }
    24  
    25  // migrateCommand initiates a model migration.
    26  type migrateCommand struct {
    27  	modelcmd.ModelCommandBase
    28  	newAPIRoot       func(jujuclient.ClientStore, string, string) (api.Connection, error)
    29  	api              migrateAPI
    30  	targetController string
    31  }
    32  
    33  type migrateAPI interface {
    34  	InitiateMigration(spec controller.MigrationSpec) (string, error)
    35  }
    36  
    37  const migrateDoc = `
    38  migrate begins the migration of a model from its current controller to
    39  a new controller. This is useful for load balancing when a controller
    40  is too busy, or as a way to upgrade a model's controller to a newer
    41  Juju version. Once complete, the model's machine and and unit agents
    42  will be connected to the new controller. The model will no longer be
    43  available at the source controller.
    44  
    45  Note that only hosted models can be migrated. Controller models can
    46  not be migrated.
    47  
    48  If the migration fails for some reason, the model be returned to its
    49  original state with the model being managed by the original
    50  controller.
    51  
    52  In order to start a migration, the target controller must be in the
    53  juju client's local configuration cache. See the juju "login" command
    54  for details of how to do this.
    55  
    56  This command only starts a model migration - it does not wait for its
    57  completion. The progress of a migration can be tracked using the
    58  "status" command and by consulting the logs.
    59  
    60  See also:
    61      login
    62      controllers
    63      status
    64  `
    65  
    66  // Info implements cmd.Command.
    67  func (c *migrateCommand) Info() *cmd.Info {
    68  	return jujucmd.Info(&cmd.Info{
    69  		Name:    "migrate",
    70  		Args:    "<model-name> <target-controller-name>",
    71  		Purpose: "Migrate a hosted model to another controller.",
    72  		Doc:     migrateDoc,
    73  	})
    74  }
    75  
    76  // Init implements cmd.Command.
    77  func (c *migrateCommand) Init(args []string) error {
    78  	if len(args) < 1 {
    79  		return errors.New("model not specified")
    80  	}
    81  	if len(args) < 2 {
    82  		return errors.New("target controller not specified")
    83  	}
    84  	if len(args) > 2 {
    85  		return errors.New("too many arguments specified")
    86  	}
    87  
    88  	c.SetModelName(args[0], false)
    89  	c.targetController = args[1]
    90  	return nil
    91  }
    92  
    93  func (c *migrateCommand) getMigrationSpec() (*controller.MigrationSpec, error) {
    94  	store := c.ClientStore()
    95  
    96  	controllerInfo, err := store.ControllerByName(c.targetController)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	accountInfo, err := store.AccountDetails(c.targetController)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	var macs []macaroon.Slice
   107  	if accountInfo.Password == "" {
   108  		var err error
   109  		macs, err = c.getTargetControllerMacaroons()
   110  		if err != nil {
   111  			return nil, errors.Trace(err)
   112  		}
   113  	}
   114  
   115  	return &controller.MigrationSpec{
   116  		TargetControllerUUID: controllerInfo.ControllerUUID,
   117  		TargetAddrs:          controllerInfo.APIEndpoints,
   118  		TargetCACert:         controllerInfo.CACert,
   119  		TargetUser:           accountInfo.User,
   120  		TargetPassword:       accountInfo.Password,
   121  		TargetMacaroons:      macs,
   122  	}, nil
   123  }
   124  
   125  // Run implements cmd.Command.
   126  func (c *migrateCommand) Run(ctx *cmd.Context) error {
   127  	spec, err := c.getMigrationSpec()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	modelName, err := c.ModelName()
   132  	if err != nil {
   133  		return errors.Trace(err)
   134  	}
   135  	uuids, err := c.ModelUUIDs([]string{modelName})
   136  	if err != nil {
   137  		return errors.Trace(err)
   138  	}
   139  	spec.ModelUUID = uuids[0]
   140  	api, err := c.getAPI()
   141  	if err != nil {
   142  		return err
   143  	}
   144  	id, err := api.InitiateMigration(*spec)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	ctx.Infof("Migration started with ID %q", id)
   149  	return nil
   150  }
   151  
   152  func (c *migrateCommand) getAPI() (migrateAPI, error) {
   153  	if c.api != nil {
   154  		return c.api, nil
   155  	}
   156  	apiRoot, err := c.NewControllerAPIRoot()
   157  	if err != nil {
   158  		return nil, errors.Trace(err)
   159  	}
   160  	return controller.NewClient(apiRoot), nil
   161  }
   162  
   163  func (c *migrateCommand) getTargetControllerMacaroons() ([]macaroon.Slice, error) {
   164  	jar, err := c.CommandBase.CookieJar(c.ClientStore(), c.targetController)
   165  	if err != nil {
   166  		return nil, errors.Trace(err)
   167  	}
   168  
   169  	// Connect to the target controller, ensuring up-to-date macaroons,
   170  	// and return the macaroons in the cookie jar for the controller.
   171  	//
   172  	// TODO(axw,mjs) add a controller API that returns a macaroon that
   173  	// may be used for the sole purpose of migration.
   174  	api, err := c.newAPIRoot(c.ClientStore(), c.targetController, "")
   175  	if err != nil {
   176  		return nil, errors.Annotate(err, "connecting to target controller")
   177  	}
   178  	defer api.Close()
   179  	return httpbakery.MacaroonsForURL(jar, api.CookieURL()), nil
   180  }