github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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.v1/httpbakery"
    10  	"gopkg.in/macaroon.v1"
    11  
    12  	"github.com/juju/juju/api"
    13  	"github.com/juju/juju/api/controller"
    14  	"github.com/juju/juju/cmd/modelcmd"
    15  	"github.com/juju/juju/jujuclient"
    16  )
    17  
    18  func newMigrateCommand() cmd.Command {
    19  	var cmd migrateCommand
    20  	cmd.newAPIRoot = cmd.JujuCommandBase.NewAPIRoot
    21  	return modelcmd.WrapController(&cmd)
    22  }
    23  
    24  // migrateCommand initiates a model migration.
    25  type migrateCommand struct {
    26  	modelcmd.ControllerCommandBase
    27  	newAPIRoot       func(jujuclient.ClientStore, string, string) (api.Connection, error)
    28  	api              migrateAPI
    29  	model            string
    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 &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.model = args[0]
    89  	c.targetController = args[1]
    90  	return nil
    91  }
    92  
    93  func (c *migrateCommand) getMigrationSpec() (*controller.MigrationSpec, error) {
    94  	store := c.ClientStore()
    95  
    96  	modelUUIDs, err := c.ModelUUIDs([]string{c.model})
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	modelUUID := modelUUIDs[0]
   101  
   102  	controllerInfo, err := store.ControllerByName(c.targetController)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	accountInfo, err := store.AccountDetails(c.targetController)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	var macs []macaroon.Slice
   113  	if accountInfo.Password == "" {
   114  		var err error
   115  		macs, err = c.getTargetControllerMacaroons()
   116  		if err != nil {
   117  			return nil, errors.Trace(err)
   118  		}
   119  	}
   120  
   121  	return &controller.MigrationSpec{
   122  		ModelUUID:            modelUUID,
   123  		TargetControllerUUID: controllerInfo.ControllerUUID,
   124  		TargetAddrs:          controllerInfo.APIEndpoints,
   125  		TargetCACert:         controllerInfo.CACert,
   126  		TargetUser:           accountInfo.User,
   127  		TargetPassword:       accountInfo.Password,
   128  		TargetMacaroons:      macs,
   129  	}, nil
   130  }
   131  
   132  // Run implements cmd.Command.
   133  func (c *migrateCommand) Run(ctx *cmd.Context) error {
   134  	spec, err := c.getMigrationSpec()
   135  	if err != nil {
   136  		return err
   137  	}
   138  	api, err := c.getAPI()
   139  	if err != nil {
   140  		return err
   141  	}
   142  	id, err := api.InitiateMigration(*spec)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	ctx.Infof("Migration started with ID %q", id)
   147  	return nil
   148  }
   149  
   150  func (c *migrateCommand) getAPI() (migrateAPI, error) {
   151  	if c.api != nil {
   152  		return c.api, nil
   153  	}
   154  	return c.NewControllerAPIClient()
   155  }
   156  
   157  func (c *migrateCommand) getTargetControllerMacaroons() ([]macaroon.Slice, error) {
   158  	apiContext, err := c.APIContext()
   159  	if err != nil {
   160  		return nil, errors.Trace(err)
   161  	}
   162  
   163  	// Connect to the target controller, ensuring up-to-date macaroons,
   164  	// and return the macaroons in the cookie jar for the controller.
   165  	//
   166  	// TODO(axw,mjs) add a controller API that returns a macaroon that
   167  	// may be used for the sole purpose of migration.
   168  	api, err := c.newAPIRoot(c.ClientStore(), c.targetController, "")
   169  	if err != nil {
   170  		return nil, errors.Annotate(err, "connecting to target controller")
   171  	}
   172  	defer api.Close()
   173  	return httpbakery.MacaroonsForURL(apiContext.Jar, api.CookieURL()), nil
   174  }