github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/modelcmd" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/environs" 23 "github.com/juju/juju/environs/bootstrap" 24 "github.com/juju/juju/environs/config" 25 ) 26 27 // NewRestoreCommand returns a command used to restore a backup. 28 func NewRestoreCommand() cmd.Command { 29 restoreCmd := &restoreCommand{} 30 restoreCmd.getEnvironFunc = restoreCmd.getEnviron 31 restoreCmd.newAPIClientFunc = func() (RestoreAPI, error) { 32 return restoreCmd.newClient() 33 } 34 restoreCmd.getArchiveFunc = getArchive 35 return modelcmd.Wrap(restoreCmd) 36 } 37 38 // restoreCommand is a subcommand of backups that implement the restore behavior 39 // it is invoked with "juju restore-backup". 40 type restoreCommand struct { 41 CommandBase 42 constraints constraints.Value 43 filename string 44 backupId string 45 bootstrap bool 46 uploadTools bool 47 48 newAPIClientFunc func() (RestoreAPI, error) 49 getEnvironFunc func(string, *params.BackupsMetadataResult) (environs.Environ, error) 50 getArchiveFunc func(string) (ArchiveReader, *params.BackupsMetadataResult, error) 51 } 52 53 // RestoreAPI is used to invoke various API calls. 54 type RestoreAPI interface { 55 // Close is taken from io.Closer. 56 Close() error 57 58 // Restore is taken from backups.Client. 59 Restore(backupId string, newClient backups.ClientConnection) error 60 61 // RestoreReader is taken from backups.Client. 62 RestoreReader(r io.ReadSeeker, meta *params.BackupsMetadataResult, newClient backups.ClientConnection) error 63 } 64 65 var restoreDoc = ` 66 Restores a backup that was previously created with "juju create-backup". 67 68 This command creates a new controller and arranges for it to replace 69 the previous controller for a model. It does *not* restore 70 an existing server to a previous state, but instead creates a new server 71 with equivalent state. As part of restore, all known instances are 72 configured to treat the new controller as their master. 73 74 The given constraints will be used to choose the new instance. 75 76 If the provided state cannot be restored, this command will fail with 77 an appropriate message. For instance, if the existing bootstrap 78 instance is already running then the command will fail with a message 79 to that effect. 80 ` 81 82 var BootstrapFunc = bootstrap.Bootstrap 83 84 // Info returns the content for --help. 85 func (c *restoreCommand) Info() *cmd.Info { 86 return &cmd.Info{ 87 Name: "restore-backup", 88 Purpose: "restore from a backup archive to a new controller", 89 Args: "", 90 Doc: strings.TrimSpace(restoreDoc), 91 } 92 } 93 94 // SetFlags handles known option flags. 95 func (c *restoreCommand) SetFlags(f *gnuflag.FlagSet) { 96 c.CommandBase.SetFlags(f) 97 f.Var(constraints.ConstraintsValue{Target: &c.constraints}, 98 "constraints", "set model constraints") 99 100 f.BoolVar(&c.bootstrap, "b", false, "bootstrap a new state machine") 101 f.StringVar(&c.filename, "file", "", "provide a file to be used as the backup.") 102 f.StringVar(&c.backupId, "id", "", "provide the name of the backup to be restored.") 103 f.BoolVar(&c.uploadTools, "upload-tools", false, "upload tools if bootstraping a new machine.") 104 } 105 106 // Init is where the preconditions for this commands can be checked. 107 func (c *restoreCommand) Init(args []string) error { 108 if c.filename == "" && c.backupId == "" { 109 return errors.Errorf("you must specify either a file or a backup id.") 110 } 111 if c.filename != "" && c.backupId != "" { 112 return errors.Errorf("you must specify either a file or a backup id but not both.") 113 } 114 if c.backupId != "" && c.bootstrap { 115 return errors.Errorf("it is not possible to rebootstrap and restore from an id.") 116 } 117 var err error 118 if c.filename != "" { 119 c.filename, err = filepath.Abs(c.filename) 120 if err != nil { 121 return errors.Trace(err) 122 } 123 } 124 return nil 125 } 126 127 // getEnviron returns the environ for the specified controller, or 128 // mocked out environ for testing. 129 func (c *restoreCommand) getEnviron(controllerName string, meta *params.BackupsMetadataResult) (environs.Environ, error) { 130 // TODO(axw) delete this and -b in 2.0-beta2. We will update bootstrap 131 // with a flag to specify a restore file. When we do that, we'll need 132 // to extract the CA cert from the backup, and we'll need to reset the 133 // password after restore so the admin user can login. 134 // We also need to store things like the admin-secret, controller 135 // certificate etc with the backup. 136 store := c.ClientStore() 137 cfg, err := modelcmd.NewGetBootstrapConfigFunc(store)(controllerName) 138 if err != nil { 139 return nil, errors.Annotate(err, "cannot restore from a machine other than the one used to bootstrap") 140 } 141 142 // Reset current model to admin so first bootstrap succeeds. 143 err = store.SetCurrentModel(controllerName, environs.AdminUser, "admin") 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 148 // Get the local admin user so we can use the password as the admin secret. 149 var adminSecret string 150 account, err := store.AccountByName(controllerName, environs.AdminUser) 151 if err == nil { 152 adminSecret = account.Password 153 } else if errors.IsNotFound(err) { 154 // No relevant local admin user so generate a new secret. 155 buf := make([]byte, 16) 156 if _, err := io.ReadFull(rand.Reader, buf); err != nil { 157 return nil, errors.Annotate(err, "generating new admin secret") 158 } 159 adminSecret = fmt.Sprintf("%x", buf) 160 } else { 161 return nil, errors.Trace(err) 162 } 163 164 // Turn on safe mode so that the newly bootstrapped instance 165 // will not destroy all the instances it does not know about. 166 // Also set the admin secret and ca cert info. 167 cfg, err = cfg.Apply(map[string]interface{}{ 168 "provisioner-safe-mode": true, 169 "admin-secret": adminSecret, 170 "ca-private-key": meta.CAPrivateKey, 171 "ca-cert": meta.CACert, 172 }) 173 if err != nil { 174 return nil, errors.Annotatef(err, "cannot enable provisioner-safe-mode") 175 } 176 return environs.New(cfg) 177 } 178 179 // rebootstrap will bootstrap a new server in safe-mode (not killing any other agent) 180 // if there is no current server available to restore to. 181 func (c *restoreCommand) rebootstrap(ctx *cmd.Context, meta *params.BackupsMetadataResult) error { 182 env, err := c.getEnvironFunc(c.ControllerName(), meta) 183 if err != nil { 184 return errors.Trace(err) 185 } 186 instanceIds, err := env.ControllerInstances() 187 if err != nil && errors.Cause(err) != environs.ErrNotBootstrapped { 188 return errors.Annotatef(err, "cannot determine controller instances") 189 } 190 if len(instanceIds) > 0 { 191 inst, err := env.Instances(instanceIds) 192 if err == nil { 193 return errors.Errorf("old bootstrap instance %q still seems to exist; will not replace", inst) 194 } 195 if err != environs.ErrNoInstances { 196 return errors.Annotatef(err, "cannot detect whether old instance is still running") 197 } 198 } 199 200 // We require a hosted model config to bootstrap. We'll fill in some defaults 201 // just to get going. The restore will clear the initial state. 202 hostedModelUUID, err := utils.NewUUID() 203 if err != nil { 204 return errors.Trace(err) 205 } 206 hostedModelConfig := map[string]interface{}{ 207 "name": "default", 208 config.UUIDKey: hostedModelUUID.String(), 209 } 210 211 args := bootstrap.BootstrapParams{ 212 ModelConstraints: c.constraints, 213 UploadTools: c.uploadTools, 214 HostedModelConfig: hostedModelConfig, 215 } 216 if err := BootstrapFunc(modelcmd.BootstrapContext(ctx), env, args); err != nil { 217 return errors.Annotatef(err, "cannot bootstrap new instance") 218 } 219 return nil 220 } 221 222 func (c *restoreCommand) newClient() (*backups.Client, error) { 223 client, err := c.NewAPIClient() 224 if err != nil { 225 return nil, errors.Trace(err) 226 } 227 backupsClient, ok := client.(*backups.Client) 228 if !ok { 229 return nil, errors.Errorf("invalid client for backups") 230 } 231 return backupsClient, nil 232 } 233 234 // Run is the entry point for this command. 235 func (c *restoreCommand) Run(ctx *cmd.Context) error { 236 if c.Log != nil { 237 if err := c.Log.Start(ctx); err != nil { 238 return err 239 } 240 } 241 242 var archive ArchiveReader 243 var meta *params.BackupsMetadataResult 244 target := c.backupId 245 if c.filename != "" { 246 // Read archive specified by the filename; 247 // we'll need the info later regardless if 248 // we need it now to rebootstrap. 249 target = c.filename 250 var err error 251 archive, meta, err = c.getArchiveFunc(c.filename) 252 if err != nil { 253 return errors.Trace(err) 254 } 255 defer archive.Close() 256 257 if c.bootstrap { 258 if err := c.rebootstrap(ctx, meta); err != nil { 259 return errors.Trace(err) 260 } 261 } 262 } 263 264 client, err := c.newAPIClientFunc() 265 if err != nil { 266 return errors.Trace(err) 267 } 268 defer client.Close() 269 270 // We have a backup client, now use the relevant method 271 // to restore the backup. 272 if c.filename != "" { 273 err = client.RestoreReader(archive, meta, c.newClient) 274 } else { 275 err = client.Restore(c.backupId, c.newClient) 276 } 277 if err != nil { 278 return nil 279 } 280 fmt.Fprintf(ctx.Stdout, "restore from %q completed\n", target) 281 return nil 282 }