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 }