oracle/pkg/agents/backup/restore.go (189 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package backup this file provides the restore and recovery functions from a // physical backup and is intended to be called from a Config Agent gRPC // server. package backup import ( "context" "fmt" "path/filepath" "sort" "strings" "time" lropb "google.golang.org/genproto/googleapis/longrunning" "k8s.io/klog/v2" "github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/pkg/agents/consts" dbdpb "github.com/GoogleCloudPlatform/elcarro-oracle-operator/oracle/pkg/agents/oracle" ) const ( maxSCNquery = ` select max(next_change#) as scn from v$archived_log where resetlogs_id=( select resetlogs_id from v$database_incarnation where status='CURRENT' ) ` restoreStmtTemplate = ` run { startup force nomount; restore spfile to '%s' from '%s'; shutdown immediate; startup nomount; restore controlfile from '%s'; startup mount; } reset database to incarnation %s; run { %s restore database; delete foreign archivelog all; } reset database to incarnation %s; ` recoverStmtTemplate = `run { recover database until %s; alter database open resetlogs; alter pluggable database all open; } ` ) type fileTime struct { name string modTime time.Time } // PhysicalRestore runs an RMAN restore and recovery. // Presently the recovery process goes up to the last SCN in the last // archived redo log. func PhysicalRestore(ctx context.Context, params *Params) (*lropb.Operation, error) { klog.InfoS("oracle/PhysicalRestore", "params", params) var channels string for i := 1; i <= int(params.DOP); i++ { channels += fmt.Sprintf(allocateChannel, i) } klog.InfoS("oracle/PhysicalRestore", "channels", channels) backupDir := consts.DefaultRMANDir if params.LocalPath != "" { backupDir = params.LocalPath } if params.GCSPath != "" { backupDir = consts.RMANStagingDir downloadReq := &dbdpb.DownloadDirectoryFromGCSRequest{ GcsPath: params.GCSPath, LocalPath: backupDir, } klog.InfoS("oracle/PhysicalRestore", "restore from gcs, downloadReq", downloadReq) if _, err := params.Client.DownloadDirectoryFromGCS(ctx, downloadReq); err != nil { return nil, fmt.Errorf("PhysicalRestore: failed to download rman backup from GCS bucket %s", err) } } klog.InfoS("oracle/PhysicalRestore", "backupDir", backupDir) resp, err := params.Client.ReadDir(ctx, &dbdpb.ReadDirRequest{ Path: backupDir, Recursive: true, }) if err != nil { return nil, fmt.Errorf("PhysicalRestore: failed to read backup dir: %v", err) } // Files stored in default format: // "nnsnf" is used to locate spfile backup piece; // "ncnnf" is used to locate control file backup piece; latestSpfileBackup, err := findLatestBackupPiece(resp, "nnsnf") if err != nil { return nil, fmt.Errorf("PhysicalRestore: failed to find latest spfile backup piece: %v", err) } latestControlfileBackup, err := findLatestBackupPiece(resp, "ncnnf") if err != nil { return nil, fmt.Errorf("PhysicalRestore: failed to find latest control file backup piece: %v", err) } // Delete spfile and datafiles. if err := deleteFilesForRestore(ctx, params.Client, params.CDBName); err != nil { klog.ErrorS(err, "PhysicalRestore: failed to delete the spfile and datafiles before restore") } // Ensures all required dirs are exist. if err := createDirsForRestore(ctx, params.Client, params.CDBName); err != nil { return nil, fmt.Errorf("PhysicalRestore: failed to createDirsForRestore: %v", err) } spfileLoc := filepath.Join( fmt.Sprintf(consts.ConfigDir, consts.DataMount, params.CDBName), fmt.Sprintf("spfile%s.ora", params.CDBName), ) restoreStmt := fmt.Sprintf(restoreStmtTemplate, spfileLoc, latestSpfileBackup, latestControlfileBackup, params.BackupIncarnation, channels, params.Incarnation) req := &dbdpb.PhysicalRestoreAsyncRequest{ SyncRequest: &dbdpb.PhysicalRestoreRequest{ RestoreStatement: restoreStmt, LatestRecoverableScnQuery: maxSCNquery, RecoverStatementTemplate: recoverStmtTemplate, }, LroInput: &dbdpb.LROInput{OperationId: params.OperationID}, } if params.EndTime != nil || params.EndSCN != 0 { req = &dbdpb.PhysicalRestoreAsyncRequest{ SyncRequest: &dbdpb.PhysicalRestoreRequest{ RestoreStatement: restoreStmt, RecoverStatementTemplate: recoverStmtTemplate, PitrRestoreInput: &dbdpb.PhysicalRestoreRequest_PITRRestoreInput{ LogGcsPath: params.LogGcsDir, Incarnation: params.Incarnation, StartTime: params.StartTime, EndTime: params.EndTime, StartScn: params.StartSCN, EndScn: params.EndSCN, }, }, LroInput: &dbdpb.LROInput{OperationId: params.OperationID}, } } operation, err := params.Client.PhysicalRestoreAsync(ctx, req) if err != nil { return nil, fmt.Errorf("oracle/PhysicalRestore: failed to create database restore request: %v", err) } return operation, nil } // findLatestBackupPiece finds the latest modified backup piece whose name contains substr. func findLatestBackupPiece(readDirResp *dbdpb.ReadDirResponse, substr string) (string, error) { var fileTimes []fileTime for _, fileInfo := range readDirResp.SubPaths { if !fileInfo.IsDir && strings.Contains(fileInfo.Name, substr) { if err := fileInfo.ModTime.CheckValid(); err != nil { return "", fmt.Errorf("findLatestBackupPiece: failed to convert timestamp: %v", err) } modTime := fileInfo.ModTime.AsTime() fileTimes = append(fileTimes, fileTime{name: fileInfo.AbsPath, modTime: modTime}) } } if len(fileTimes) < 1 { return "", fmt.Errorf("findLatestBackupPiece: failed to find candidates for substr %s: %d", substr, len(fileTimes)) } for i, t := range fileTimes { klog.InfoS(fmt.Sprintf("%s time", substr), "index", i, "name", t.name, "modTime", t.modTime) } sort.Slice(fileTimes, func(i, j int) bool { return fileTimes[i].modTime.After(fileTimes[j].modTime) }) klog.InfoS(fmt.Sprintf("findLatestBackupPiece: sorted %s files", substr), "fileTimes", fileTimes) return fileTimes[0].name, nil } // Delete spfile and datafiles. func deleteFilesForRestore(ctx context.Context, dbdClient dbdpb.DatabaseDaemonClient, cdbName string) error { spfileLoc := filepath.Join( fmt.Sprintf(consts.ConfigDir, consts.DataMount, cdbName), fmt.Sprintf("spfile%s.ora", cdbName), ) if _, err := dbdClient.DeleteDir(ctx, &dbdpb.DeleteDirRequest{Path: spfileLoc}); err != nil { klog.ErrorS(err, "deleteDirsForRestore: failed to delete the spfile before restore") } dataDir := filepath.Join(consts.OracleBase, "oradata", "*") if _, err := dbdClient.DeleteDir(ctx, &dbdpb.DeleteDirRequest{Path: dataDir}); err != nil { klog.ErrorS(err, "deleteDirsForRestore: failed to delete the data files before restore", "dataDir", dataDir) } return nil } // createDirsForRestore ensures all required dirs exist for restore. func createDirsForRestore(ctx context.Context, dbdClient dbdpb.DatabaseDaemonClient, cdbName string) error { toCreate := []string{ // adump dir filepath.Join(consts.OracleBase, "admin", cdbName, "adump"), // configfiles dir fmt.Sprintf(consts.ConfigDir, consts.DataMount, cdbName), // datafiles dir fmt.Sprintf(consts.DataDir, consts.DataMount, cdbName), // flash dir fmt.Sprintf(consts.RecoveryAreaDir, consts.LogMount, cdbName), } var dirs []*dbdpb.CreateDirsRequest_DirInfo for _, d := range toCreate { dirs = append(dirs, &dbdpb.CreateDirsRequest_DirInfo{ Path: d, Perm: 0760, }) } if _, err := dbdClient.CreateDirs(ctx, &dbdpb.CreateDirsRequest{ Dirs: dirs, }); err != nil { return fmt.Errorf("createDirsForRestore: failed to create dirs %v: %v", dirs, err) } return nil }