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 }