Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 64 additions & 6 deletions internal/oraclehandlers/patching.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,71 @@ func enableAutostart(ctx context.Context, logger *zap.SugaredLogger, params map[

// RunDatapatch implements the oracle_run_datapatch guest action.
func RunDatapatch(ctx context.Context, command *gpb.Command, cloudProperties *metadataserver.CloudProperties) *gpb.CommandResult {
log.CtxLogger(ctx).Info("oracle_run_datapatch handler called")
// TODO: Implement oracle_run_datapatch handler.
return &gpb.CommandResult{
Command: command,
ExitCode: 1,
Stdout: "oracle_run_datapatch not implemented.",
params := command.GetAgentCommand().GetParameters()
logger := log.CtxLogger(ctx)
if result := validateParams(ctx, logger, command, params, []string{"oracle_sid", "oracle_home", "oracle_user"}); result != nil {
return result
}

logger = logger.With("oracle_sid", params["oracle_sid"], "oracle_home", params["oracle_home"], "oracle_user", params["oracle_user"])
logger.Info("oracle_run_datapatch handler called")

if err := runDatapatch(ctx, logger, params); err != nil {
logger.Warnw("RunDatapatch failed", "error", err)
return commandResult(ctx, logger, command, "", "", codepb.Code_INTERNAL, err.Error(), err)
}

return commandResult(ctx, logger, command, "Datapatch execution completed successfully", "", codepb.Code_OK, "Datapatch execution completed successfully", nil)
}

func runDatapatch(ctx context.Context, logger *zap.SugaredLogger, params map[string]string) error {
// Open all PDBs.
// We ignore errors here because the database might not be a CDB, or PDBs might already be open.
pdbStdout, pdbStderr, pdbErr := runSQL(ctx, params, "ALTER PLUGGABLE DATABASE ALL OPEN;", 60, false)
if pdbErr != nil {
logger.Warnw("Attempted to open all PDBs", "stdout", pdbStdout, "stderr", pdbStderr, "error", pdbErr)
} else {
logger.Infow("Opened all PDBs", "stdout", pdbStdout)
}

// Run datapatch.
oracleHome := params["oracle_home"]
oracleUser := params["oracle_user"]
oracleSID := params["oracle_sid"]
datapatchPath := filepath.Join(oracleHome, "OPatch", "datapatch")

// datapatch can take a significant amount of time.
logger.Info("Executing datapatch...")
datapatchRes := executeCommand(ctx, commandlineexecutor.Params{
Executable: datapatchPath,
Args: []string{"-verbose"},
User: oracleUser,
Env: []string{"ORACLE_HOME=" + oracleHome, "ORACLE_SID=" + oracleSID, "LD_LIBRARY_PATH=" + filepath.Join(oracleHome, "lib"), "PATH=" + filepath.Join(oracleHome, "bin") + ":/usr/bin:/bin"},
Timeout: 3600, // 1 hour timeout
})

logger.Infow("Datapatch execution result", "stdout", datapatchRes.StdOut, "stderr", datapatchRes.StdErr, "exit_code", datapatchRes.ExitCode)

if datapatchRes.ExitCode != 0 {
return fmt.Errorf("datapatch failed with exit code %d: %s", datapatchRes.ExitCode, datapatchRes.StdErr)
}

// Recompile invalid objects (utlrp.sql).
logger.Info("Executing utlrp.sql...")
utlrpStdout, utlrpStderr, utlrpErr := runSQL(ctx, params, "@?/rdbms/admin/utlrp", 3600, true)
if utlrpErr != nil {
return fmt.Errorf("utlrp execution failed: %w", utlrpErr)
}
logger.Infow("utlrp execution result", "stdout", utlrpStdout, "stderr", utlrpStderr)

// Log patch registry for debugging.
regStdout, regStderr, regErr := runSQL(ctx, params, "SELECT action_time, action, status, patch_id FROM dba_registry_sqlpatch ORDER BY action_time;", 60, false)
if regErr != nil {
logger.Warnw("Failed to query dba_registry_sqlpatch", "stdout", regStdout, "stderr", regStderr, "error", regErr)
}
logger.Infow("Patch registry status", "stdout", regStdout)

return nil
}

// DisableRestrictedSession implements the oracle_disable_restricted_mode guest action.
Expand Down
113 changes: 102 additions & 11 deletions internal/oraclehandlers/patching_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,20 +703,111 @@ func TestDetectStartupMechanism(t *testing.T) {
}
}

func TestRunDatapatch_NotImplemented(t *testing.T) {
command := &gpb.Command{
CommandType: &gpb.Command_AgentCommand{
AgentCommand: &gpb.AgentCommand{
Command: "oracle_run_datapatch",
func TestRunDatapatch(t *testing.T) {
defaultParams := map[string]string{
"oracle_sid": "ORCL",
"oracle_home": "/u01/app/oracle/product/19.0.0/dbhome_1",
"oracle_user": "oracle",
}

tests := []struct {
name string
params map[string]string
mockSQL map[string]*commandlineexecutor.Result // Mocks for runSQL
mockCmds map[string]commandlineexecutor.Result // Mocks for executeCommand (datapatch)
wantErrorCode codepb.Code
}{
{
name: "Success",
params: defaultParams,
mockSQL: map[string]*commandlineexecutor.Result{
"ALTER PLUGGABLE DATABASE ALL OPEN;": {ExitCode: 0, StdOut: "Pluggable database altered."},
"@?/rdbms/admin/utlrp": {ExitCode: 0, StdOut: "PL/SQL procedure successfully completed."},
"SELECT action_time, action, status, patch_id FROM dba_registry_sqlpatch ORDER BY action_time;": {ExitCode: 0, StdOut: "PATCH INFO"},
},
mockCmds: map[string]commandlineexecutor.Result{
"datapatch": {ExitCode: 0, StdOut: "Datapatch successful"},
},
wantErrorCode: codepb.Code_OK,
},
{
name: "PDBOpenFailProceeds",
params: defaultParams,
mockSQL: map[string]*commandlineexecutor.Result{
"ALTER PLUGGABLE DATABASE ALL OPEN;": {ExitCode: 1, Error: fmt.Errorf("ORA-65000: missing or invalid pluggable database name")},
"@?/rdbms/admin/utlrp": {ExitCode: 0, StdOut: "PL/SQL procedure successfully completed."},
"SELECT action_time, action, status, patch_id FROM dba_registry_sqlpatch ORDER BY action_time;": {ExitCode: 0, StdOut: "PATCH INFO"},
},
mockCmds: map[string]commandlineexecutor.Result{
"datapatch": {ExitCode: 0, StdOut: "Datapatch successful"},
},
wantErrorCode: codepb.Code_OK,
},
{
name: "DatapatchFail",
params: defaultParams,
mockSQL: map[string]*commandlineexecutor.Result{
"ALTER PLUGGABLE DATABASE ALL OPEN;": {ExitCode: 0},
},
mockCmds: map[string]commandlineexecutor.Result{
"datapatch": {ExitCode: 1, StdErr: "Datapatch failed"},
},
wantErrorCode: codepb.Code_INTERNAL,
},
{
name: "UtlrpFail",
params: defaultParams,
mockSQL: map[string]*commandlineexecutor.Result{
"ALTER PLUGGABLE DATABASE ALL OPEN;": {ExitCode: 0},
"@?/rdbms/admin/utlrp": {ExitCode: 1, Error: fmt.Errorf("utlrp failed")},
},
mockCmds: map[string]commandlineexecutor.Result{
"datapatch": {ExitCode: 0},
},
wantErrorCode: codepb.Code_INTERNAL,
},
{
name: "ValidationFailMissingParams",
params: map[string]string{}, // Missing params
wantErrorCode: codepb.Code_INVALID_ARGUMENT,
},
}
result := RunDatapatch(context.Background(), command, nil)
if result.GetExitCode() != 1 {
t.Errorf("RunDatapatch() returned exit code %d, want 1", result.GetExitCode())
}
if !strings.Contains(result.GetStdout(), "not implemented") {
t.Errorf("RunDatapatch() returned stdout %q, want 'not implemented'", result.GetStdout())

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Mock runSQL
origRunSQL := runSQL
defer func() { runSQL = origRunSQL }()
runSQL = createMockRunSQL(tc.mockSQL)

// Mock executeCommand for datapatch
origExecuteCommand := executeCommand
defer func() { executeCommand = origExecuteCommand }()
executeCommand = func(ctx context.Context, params commandlineexecutor.Params) commandlineexecutor.Result {
if strings.Contains(params.Executable, "datapatch") {
return tc.mockCmds["datapatch"]
}
return commandlineexecutor.Result{ExitCode: 1, StdErr: "Unknown command: " + params.Executable}
}

command := &gpb.Command{
CommandType: &gpb.Command_AgentCommand{
AgentCommand: &gpb.AgentCommand{
Command: "oracle_run_datapatch",
Parameters: tc.params,
},
},
}
result := RunDatapatch(context.Background(), command, nil)

s := &spb.Status{}
if err := anypb.UnmarshalTo(result.Payload, s, proto.UnmarshalOptions{}); err != nil {
t.Fatalf("Failed to unmarshal payload: %v", err)
}
if s.Code != int32(tc.wantErrorCode) {
t.Errorf("RunDatapatch() with params %v returned error code %d, want %d", tc.params, s.Code, tc.wantErrorCode)
}
})
}
}

Expand Down