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 }