// 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 }
// 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 }