Skip to content

Commit

Permalink
Add support for restoration of disks in scaleout systems
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 697562799
  • Loading branch information
Neejor Chakma authored and copybara-github committed Nov 18, 2024
1 parent 1bb58ae commit 980ed20
Show file tree
Hide file tree
Showing 17 changed files with 3,083 additions and 883 deletions.
55 changes: 55 additions & 0 deletions internal/hanabackup/hanabackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"

Expand All @@ -30,6 +31,14 @@ import (
"github.com/GoogleCloudPlatform/sapagent/shared/log"
)

var (
// sapServicesStartsrvPattern captures the sapstartsrv path in /usr/sap/sapservices.
// Example: "/usr/sap/DEV/ASCS01/exe/sapstartsrv pf=/usr/sap/DEV/SYS/profile/DEV_ASCS01_dnwh75ldbci -D -u devadm"
// is parsed as "sapstartsrv pf=/usr/sap/DEV/SYS/profile/DEV_ASCS01".
// Additional possibilities include sapstartsrv pf=/sapmnt/PRD/profile/PRD_ASCS01_alidascs11
sapServicesStartsrvPattern = regexp.MustCompile(`sapstartsrv pf=/(usr/sap|sapmnt)/([A-Z][A-Z0-9]{2})[/a-zA-Z0-9]*/profile/([A-Z][A-Z0-9]{2})_([a-zA-Z]+)([0-9]+)`)
)

// ParseBasePath parses the base path from the global.ini file.
func ParseBasePath(ctx context.Context, pattern string, exec commandlineexecutor.Execute) (string, error) {
args := `-c 'grep ` + pattern + ` /usr/sap/*/SYS/global/hdb/custom/config/global.ini | cut -d= -f 2'`
Expand Down Expand Up @@ -322,3 +331,49 @@ func ReadKey(file, diskURI string, read configuration.ReadConfigFile) (string, e
}
return "", fmt.Errorf("no matching key for for the disk")
}

// CheckTopology checks if topology of the system this instance belongs to is scaleout or not.
// If it is scaleout, it returns true, otherwise false.
func CheckTopology(ctx context.Context, exec commandlineexecutor.Execute, SID string) (bool, error) {
instanceNumber, err := getInstanceNumber(ctx, exec, SID)
if err != nil {
return false, err
}
cmd := commandlineexecutor.Params{
Executable: "sudo",
Args: []string{"-i", "-u", fmt.Sprintf("%sadm", strings.ToLower(SID)), "sapcontrol", "-nr", instanceNumber, "-function", "GetSystemInstanceList"},
}
res := exec(ctx, cmd)
if res.Error != nil {
return false, fmt.Errorf("failed to verify topology: %w", res.Error)
}
log.CtxLogger(ctx).Debugw("`sapcontrol -nr %s -function GetSystemInstanceList` returned", instanceNumber, "stdout", res.StdOut, "stderr", res.StdErr, "error", res.Error)

lines := strings.Split(res.StdOut, "\n")
if len(lines) == 6 {
return false, nil
} else if len(lines) > 6 {
return true, nil
}
return false, fmt.Errorf("no SAP instances found")
}

// getInstanceNumber returns the instance number of the instance.
func getInstanceNumber(ctx context.Context, exec commandlineexecutor.Execute, SID string) (string, error) {
result := exec(ctx, commandlineexecutor.Params{
Executable: "grep",
ArgsToSplit: fmt.Sprintf("'pf=/usr/sap/%s' /usr/sap/sapservices", SID),
})
log.CtxLogger(ctx).Debugw("`grep 'pf=' /usr/sap/sapservices` returned", "stdout", result.StdOut, "stderr", result.StdErr, "error", result.Error)
if result.Error != nil {
return "", result.Error
}

path := sapServicesStartsrvPattern.FindStringSubmatch(result.StdOut)
if len(path) < 6 {
return "", fmt.Errorf("no SAP Instance found")
}
log.CtxLogger(ctx).Debugw("SAP Instance found", "instanceNumber", path[5])

return path[5], nil
}
185 changes: 185 additions & 0 deletions internal/hanabackup/hanabackup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,188 @@ func TestReadDataDirMountPath(t *testing.T) {
})
}
}

func TestCheckTopology(t *testing.T) {
tests := []struct {
name string
exec commandlineexecutor.Execute
SID string
want bool
wantErr error
}{
{
name: "InstanceNotFound",
exec: func(context.Context, commandlineexecutor.Params) commandlineexecutor.Result {
return commandlineexecutor.Result{
StdOut: "",
StdErr: "",
Error: cmpopts.AnyError,
ExitCode: 1,
}
},
SID: "SID",
want: false,
wantErr: cmpopts.AnyError,
},
{
name: "NoSAPInstancesFound",
exec: func(ctx context.Context, params commandlineexecutor.Params) commandlineexecutor.Result {
if params.Executable == "grep" {
return commandlineexecutor.Result{
StdOut: "systemctl --no-ask-password start SAPSID_00 # sapstartsrv pf=/usr/sap/SID/SYS/profile/SID_HDB00_my-instance\n",
StdErr: "",
Error: nil,
ExitCode: 0,
}
}
return commandlineexecutor.Result{
StdOut: `
17.11.2024 07:57:08
GetSystemInstanceList
OK
hostname, instanceNr, httpPort, httpsPort, startPriority, features, dispstatus`,
StdErr: "",
Error: nil,
ExitCode: 0,
}
},
SID: "SID",
want: false,
wantErr: cmpopts.AnyError,
},
{
name: "ScaleoutTopology",
exec: func(ctx context.Context, params commandlineexecutor.Params) commandlineexecutor.Result {
if params.Executable == "grep" {
return commandlineexecutor.Result{
StdOut: "systemctl --no-ask-password start SAPSID_00 # sapstartsrv pf=/usr/sap/SID/SYS/profile/SID_HDB00_my-instance\n",
StdErr: "",
Error: nil,
ExitCode: 0,
}
}
return commandlineexecutor.Result{
StdOut: `
17.11.2024 07:57:08
GetSystemInstanceList
OK
hostname, instanceNr, httpPort, httpsPort, startPriority, features, dispstatus
rb-scaleout, 12, 51213, 51214, 0.3, HDB|HDB_WORKER, GREEN
rb-scaleoutw1, 12, 51213, 51214, 0.3, HDB|HDB_WORKER, GREEN`,
StdErr: "",
Error: nil,
ExitCode: 0,
}
},
SID: "SID",
want: true,
wantErr: nil,
},
{
name: "ScaleupTopology",
exec: func(ctx context.Context, params commandlineexecutor.Params) commandlineexecutor.Result {
if params.Executable == "grep" {
return commandlineexecutor.Result{
StdOut: "systemctl --no-ask-password start SAPSID_00 # sapstartsrv pf=/usr/sap/SID/SYS/profile/SID_HDB00_my-instance\n",
StdErr: "",
Error: nil,
ExitCode: 0,
}
}
return commandlineexecutor.Result{
StdOut: `
17.11.2024 07:57:08
GetSystemInstanceList
OK
hostname, instanceNr, httpPort, httpsPort, startPriority, features, dispstatus
rb-scaleout, 12, 51213, 51214, 0.3, HDB|HDB_WORKER, GREEN`,
StdErr: "",
Error: nil,
ExitCode: 0,
}
},
SID: "SID",
want: false,
wantErr: nil,
},
}

ctx := context.Background()
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := CheckTopology(ctx, tc.exec, tc.SID)
if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
t.Errorf("CheckTopology(%v, %q) = %v, want: %v", tc.exec, tc.SID, err, tc.wantErr)
}
if got != tc.want {
t.Errorf("CheckTopology(%v, %q) = %v, want: %v", tc.exec, tc.SID, got, tc.want)
}
})
}
}

func TestGetInstanceNumber(t *testing.T) {
tests := []struct {
name string
exec commandlineexecutor.Execute
SID string
wantInstanceNumber string
wantErr error
}{
{
name: "Failure",
exec: func(context.Context, commandlineexecutor.Params) commandlineexecutor.Result {
return commandlineexecutor.Result{
StdOut: "",
StdErr: "",
Error: cmpopts.AnyError,
ExitCode: 1,
}
},
SID: "SID",
wantErr: cmpopts.AnyError,
},
{
name: "NoInstanceFound",
exec: func(context.Context, commandlineexecutor.Params) commandlineexecutor.Result {
return commandlineexecutor.Result{
StdOut: "",
StdErr: "",
Error: nil,
ExitCode: 0,
}
},
SID: "SID",
wantInstanceNumber: "",
wantErr: cmpopts.AnyError,
},
{
name: "Success",
exec: func(context.Context, commandlineexecutor.Params) commandlineexecutor.Result {
return commandlineexecutor.Result{
StdOut: "systemctl --no-ask-password start SAPSID_00 # sapstartsrv pf=/usr/sap/SID/SYS/profile/SID_HDB00_my-instance\n",
StdErr: "",
Error: nil,
ExitCode: 0,
}
},
SID: "SID",
wantInstanceNumber: "00",
wantErr: nil,
},
}

ctx := context.Background()

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := getInstanceNumber(ctx, tc.exec, tc.SID)
if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
t.Errorf("getInstanceNumber(%v, %q) = %v, want: %v", tc.exec, tc.SID, err, tc.wantErr)
}
if got != tc.wantInstanceNumber {
t.Errorf("getInstanceNumber(%v, %q) = %v, want: %v", tc.exec, tc.SID, got, tc.wantInstanceNumber)
}
})
}
}
Loading

0 comments on commit 980ed20

Please sign in to comment.