// Restore is the main entry point for backup restore. If there is no // appropriate backup on the BackupStorage, Restore logs an error // and returns ErrNoBackup. Any other error is returned. func Restore( ctx context.Context, mysqld MysqlDaemon, dir string, restoreConcurrency int, hookExtraEnv map[string]string, localMetadata map[string]string, logger logutil.Logger, deleteBeforeRestore bool) (replication.Position, error) { // find the right backup handle: most recent one, with a MANIFEST logger.Infof("Restore: looking for a suitable backup to restore") bs, err := backupstorage.GetBackupStorage() if err != nil { return replication.Position{}, err } defer bs.Close() bhs, err := bs.ListBackups(dir) if err != nil { return replication.Position{}, fmt.Errorf("ListBackups failed: %v", err) } var bh backupstorage.BackupHandle var bm BackupManifest var toRestore int for toRestore = len(bhs) - 1; toRestore >= 0; toRestore-- { bh = bhs[toRestore] rc, err := bh.ReadFile(backupManifest) if err != nil { log.Warningf("Possibly incomplete backup %v in directory %v on BackupStorage: can't read MANIFEST: %v)", bh.Name(), dir, err) continue } err = json.NewDecoder(rc).Decode(&bm) rc.Close() if err != nil { log.Warningf("Possibly incomplete backup %v in directory %v on BackupStorage (cannot JSON decode MANIFEST: %v)", bh.Name(), dir, err) continue } logger.Infof("Restore: found backup %v %v to restore with %v files", bh.Directory(), bh.Name(), len(bm.FileEntries)) break } if toRestore < 0 { logger.Errorf("No backup to restore on BackupStorage for directory %v. Starting up empty.", dir) if err = populateLocalMetadata(mysqld, localMetadata); err == nil { err = ErrNoBackup } return replication.Position{}, err } if !deleteBeforeRestore { logger.Infof("Restore: checking no existing data is present") ok, err := checkNoDB(ctx, mysqld) if err != nil { return replication.Position{}, err } if !ok { logger.Infof("Auto-restore is enabled, but mysqld already contains data. Assuming vttablet was just restarted.") if err = populateLocalMetadata(mysqld, localMetadata); err == nil { err = ErrExistingDB } return replication.Position{}, err } } logger.Infof("Restore: shutdown mysqld") err = mysqld.Shutdown(ctx, true) if err != nil { return replication.Position{}, err } logger.Infof("Restore: deleting existing files") if err := removeExistingFiles(mysqld.Cnf()); err != nil { return replication.Position{}, err } logger.Infof("Restore: reinit config file") err = mysqld.ReinitConfig(ctx) if err != nil { return replication.Position{}, err } logger.Infof("Restore: copying all files") if err := restoreFiles(mysqld.Cnf(), bh, bm.FileEntries, restoreConcurrency); err != nil { return replication.Position{}, err } // mysqld needs to be running in order for mysql_upgrade to work. // If we've just restored from a backup from previous MySQL version then mysqld // may fail to start due to a different structure of mysql.* tables. The flag // --skip-grant-tables ensures that these tables are not read until mysql_upgrade // is executed. And since with --skip-grant-tables anyone can connect to MySQL // without password, we are passing --skip-networking to greatly reduce the set // of those who can connect. logger.Infof("Restore: starting mysqld for mysql_upgrade") err = mysqld.Start(ctx, "--skip-grant-tables", "--skip-networking") if err != nil { return replication.Position{}, err } logger.Infof("Restore: running mysql_upgrade") if err := mysqld.RunMysqlUpgrade(); err != nil { return replication.Position{}, fmt.Errorf("mysql_upgrade failed: %v", err) } // Populate local_metadata before starting without --skip-networking, // so it's there before we start announcing ourselves. logger.Infof("Restore: populating local_metadata") err = populateLocalMetadata(mysqld, localMetadata) if err != nil { return replication.Position{}, err } // The MySQL manual recommends restarting mysqld after running mysql_upgrade, // so that any changes made to system tables take effect. logger.Infof("Restore: restarting mysqld after mysql_upgrade") err = mysqld.Shutdown(ctx, true) if err != nil { return replication.Position{}, err } err = mysqld.Start(ctx) if err != nil { return replication.Position{}, err } return bm.Position, nil }
func backupFiles(mysqld MysqlDaemon, logger logutil.Logger, bh backupstorage.BackupHandle, fes []FileEntry, replicationPosition replication.Position, backupConcurrency int) (err error) { sema := sync2.NewSemaphore(backupConcurrency, 0) rec := concurrency.AllErrorRecorder{} wg := sync.WaitGroup{} for i, fe := range fes { wg.Add(1) go func(i int, fe FileEntry) { defer wg.Done() // wait until we are ready to go, skip if we already // encountered an error sema.Acquire() defer sema.Release() if rec.HasErrors() { return } // open the source file for reading source, err := fe.open(mysqld.Cnf(), true) if err != nil { rec.RecordError(err) return } defer source.Close() // open the destination file for writing, and a buffer name := fmt.Sprintf("%v", i) wc, err := bh.AddFile(name) if err != nil { rec.RecordError(fmt.Errorf("cannot add file: %v", err)) return } defer func() { rec.RecordError(wc.Close()) }() dst := bufio.NewWriterSize(wc, 2*1024*1024) // create the hasher and the tee on top hasher := newHasher() tee := io.MultiWriter(dst, hasher) // create the gzip compression filter gzip, err := cgzip.NewWriterLevel(tee, cgzip.Z_BEST_SPEED) if err != nil { rec.RecordError(fmt.Errorf("cannot create gziper: %v", err)) return } // copy from the source file to gzip to tee to output file and hasher _, err = io.Copy(gzip, source) if err != nil { rec.RecordError(fmt.Errorf("cannot copy data: %v", err)) return } // close gzip to flush it, after that the hash is good if err = gzip.Close(); err != nil { rec.RecordError(fmt.Errorf("cannot close gzip: %v", err)) return } // flush the buffer to finish writing, save the hash rec.RecordError(dst.Flush()) fes[i].Hash = hasher.HashString() }(i, fe) } wg.Wait() if rec.HasErrors() { return rec.Error() } // open the MANIFEST wc, err := bh.AddFile(backupManifest) if err != nil { return fmt.Errorf("cannot add %v to backup: %v", backupManifest, err) } defer func() { if closeErr := wc.Close(); err == nil { err = closeErr } }() // JSON-encode and write the MANIFEST bm := &BackupManifest{ FileEntries: fes, Position: replicationPosition, } data, err := json.MarshalIndent(bm, "", " ") if err != nil { return fmt.Errorf("cannot JSON encode %v: %v", backupManifest, err) } if _, err := wc.Write([]byte(data)); err != nil { return fmt.Errorf("cannot write %v: %v", backupManifest, err) } return nil }
// restoreFiles will copy all the files from the BackupStorage to the // right place func restoreFiles(cnf *Mycnf, bh backupstorage.BackupHandle, fes []FileEntry, restoreConcurrency int) error { sema := sync2.NewSemaphore(restoreConcurrency, 0) rec := concurrency.AllErrorRecorder{} wg := sync.WaitGroup{} for i, fe := range fes { wg.Add(1) go func(i int, fe FileEntry) { defer wg.Done() // wait until we are ready to go, skip if we already // encountered an error sema.Acquire() defer sema.Release() if rec.HasErrors() { return } // open the source file for reading name := fmt.Sprintf("%v", i) source, err := bh.ReadFile(name) if err != nil { rec.RecordError(err) return } defer source.Close() // open the destination file for writing dstFile, err := fe.open(cnf, false) if err != nil { rec.RecordError(err) return } defer func() { rec.RecordError(dstFile.Close()) }() // create a buffering output dst := bufio.NewWriterSize(dstFile, 2*1024*1024) // create hash to write the compressed data to hasher := newHasher() // create a Tee: we split the input into the hasher // and into the gunziper tee := io.TeeReader(source, hasher) // create the uncompresser gz, err := cgzip.NewReader(tee) if err != nil { rec.RecordError(err) return } defer func() { rec.RecordError(gz.Close()) }() // copy the data. Will also write to the hasher if _, err = io.Copy(dst, gz); err != nil { rec.RecordError(err) return } // check the hash hash := hasher.HashString() if hash != fe.Hash { rec.RecordError(fmt.Errorf("hash mismatch for %v, got %v expected %v", fe.Name, hash, fe.Hash)) return } // flush the buffer rec.RecordError(dst.Flush()) }(i, fe) } wg.Wait() return rec.Error() }
// Restore is the main entry point for backup restore. If there is no // appropriate backup on the BackupStorage, Restore logs an error // and returns ErrNoBackup. Any other error is returned. func Restore(ctx context.Context, mysqld MysqlDaemon, dir string, restoreConcurrency int, hookExtraEnv map[string]string) (replication.Position, error) { // find the right backup handle: most recent one, with a MANIFEST log.Infof("Restore: looking for a suitable backup to restore") bs, err := backupstorage.GetBackupStorage() if err != nil { return replication.Position{}, err } defer bs.Close() bhs, err := bs.ListBackups(dir) if err != nil { return replication.Position{}, fmt.Errorf("ListBackups failed: %v", err) } var bh backupstorage.BackupHandle var bm BackupManifest var toRestore int for toRestore = len(bhs) - 1; toRestore >= 0; toRestore-- { bh = bhs[toRestore] rc, err := bh.ReadFile(backupManifest) if err != nil { log.Warningf("Possibly incomplete backup %v in directory %v on BackupStorage: can't read MANIFEST: %v)", bh.Name(), dir, err) continue } err = json.NewDecoder(rc).Decode(&bm) rc.Close() if err != nil { log.Warningf("Possibly incomplete backup %v in directory %v on BackupStorage (cannot JSON decode MANIFEST: %v)", bh.Name(), dir, err) continue } log.Infof("Restore: found backup %v %v to restore with %v files", bh.Directory(), bh.Name(), len(bm.FileEntries)) break } if toRestore < 0 { log.Errorf("No backup to restore on BackupStorage for directory %v", dir) return replication.Position{}, ErrNoBackup } log.Infof("Restore: checking no existing data is present") ok, err := checkNoDB(ctx, mysqld) if err != nil { return replication.Position{}, err } if !ok { return replication.Position{}, ErrExistingDB } log.Infof("Restore: shutdown mysqld") err = mysqld.Shutdown(ctx, true) if err != nil { return replication.Position{}, err } log.Infof("Restore: deleting existing files") if err := removeExistingFiles(mysqld.Cnf()); err != nil { return replication.Position{}, err } log.Infof("Restore: copying all files") if err := restoreFiles(mysqld.Cnf(), bh, bm.FileEntries, restoreConcurrency); err != nil { return replication.Position{}, err } // mysqld needs to be running in order for mysql_upgrade to work. log.Infof("Restore: starting mysqld for mysql_upgrade") err = mysqld.Start(ctx) if err != nil { return replication.Position{}, err } log.Infof("Restore: running mysql_upgrade") if err := mysqld.RunMysqlUpgrade(); err != nil { return replication.Position{}, fmt.Errorf("mysql_upgrade failed: %v", err) } // The MySQL manual recommends restarting mysqld after running mysql_upgrade, // so that any changes made to system tables take effect. log.Infof("Restore: restarting mysqld after mysql_upgrade") err = mysqld.Shutdown(ctx, true) if err != nil { return replication.Position{}, err } err = mysqld.Start(ctx) if err != nil { return replication.Position{}, err } return bm.Position, nil }