Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Visit the docs below to learn about *ramenctl* commands:
- [test](docs/test.md)
- [validate](docs/validate.md)
- [gather](docs/gather.md)
- [failover](docs/failover.md) - ⚠️ Requires [Ramen PR #2416](https://github.com/RamenDR/ramen/pull/2416)

Check the guides below to learn more:

Expand Down
52 changes: 52 additions & 0 deletions cmd/commands/failover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: The RamenDR authors
// SPDX-License-Identifier: Apache-2.0

package commands

import (
"github.com/spf13/cobra"

"github.com/ramendr/ramenctl/pkg/console"
"github.com/ramendr/ramenctl/pkg/failover"
)

var FailoverCmd = &cobra.Command{
Use: "failover",
Short: "Manage application failover operations",
}

var FailoverDryRunCmd = &cobra.Command{
Use: "dry-run",
Short: "Test failover without affecting the primary application (DRY-RUN mode)",
Long: `Test failover to the secondary cluster without affecting the primary application.

This is a DRY-RUN operation that:
- Starts the application on the secondary cluster
- Keeps the primary application running
- Allows you to verify DR readiness without risk

The application reaches "TestingFailover" progression when the dry-run succeeds.

Use --abort to revert the dry-run test and return to the original state.`,
Run: func(c *cobra.Command, args []string) {
if abortDryRun {
if err := failover.AbortDryRun(configFile, drpcName, drpcNamespace); err != nil {
console.Fatal(err)
}
} else {
if err := failover.TestDryRun(configFile, outputDir, drpcName, drpcNamespace); err != nil {
console.Fatal(err)
}
}
},
}

var abortDryRun bool

func init() {
addDRPCFlags(FailoverDryRunCmd)
FailoverDryRunCmd.Flags().BoolVar(&abortDryRun, "abort", false, "abort the dry-run failover test and revert to original state")
FailoverDryRunCmd.Flags().StringVarP(&outputDir, "output", "o", "", "output directory for test report (optional, only used without --abort)")

FailoverCmd.AddCommand(FailoverDryRunCmd)
}
1 change: 1 addition & 0 deletions cmd/ramenctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func main() {
commands.TestCmd,
commands.GatherCmd,
commands.ValidateCmd,
commands.FailoverCmd,
)

err := commands.RootCmd.Execute()
Expand Down
247 changes: 247 additions & 0 deletions docs/failover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<!-- SPDX-FileCopyrightText: The RamenDR authors -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# ramenctl failover

The failover command manages application failover operations for disaster recovery
protected applications.

```console
$ ramenctl failover -h
Manage application failover operations

Usage:
ramenctl failover [command]

Available Commands:
dry-run Test failover without affecting the primary application (DRY-RUN mode)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ramenctl failover dry-run and ramenctl failover dry-run --abort do not make sense.

I think this should be an a separate command like failover-test, or an option for the failover command.


Flags:
-h, --help help for failover

Global Flags:
-c, --config string configuration file (default "config.yaml")

Use "ramenctl failover [command] --help" for more information about a command.
```

> [!IMPORTANT]
> The failover command requires a configuration file. See [init](docs/init.md) to
> learn how to create one.

> [!IMPORTANT]
> This command requires Ramen PR [#2416](https://github.com/RamenDR/ramen/pull/2416)
> to be merged. The `DryRun` field is not yet available in the current Ramen API version.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This note is should not be in the docs.

Note that ramenctl must work with any ramen version. The command should check if the installed ramen version supports this feature and fail gracefully if not.


## failover dry-run

The failover dry-run command tests failover to the secondary cluster without
affecting the primary application. This allows you to verify DR readiness without
risk to production workloads.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true - using this command risk your production workload - if you need to do a real failover the failover will require a full sync. The document should have a big warning about this.


### What is a dry-run failover?

A dry-run failover is a non-destructive test that:
- Starts the application on the secondary cluster
- Keeps the primary application running
- Validates that failover would work in a real disaster
- Can be safely aborted and reverted

This is achieved by setting `dryRun: true` in the DRPC spec along with
`action: Failover`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation suggests that the right user interface is:

ramenctl failover --dry-run ...


### Looking up applications

To run the dry-run failover command, we need to find the protected application
name and namespace. Run the following command:

```console
$ kubectl get drpc -A --context hub
NAMESPACE NAME AGE PREFERREDCLUSTER FAILOVERCLUSTER DESIREDSTATE CURRENTSTATE
argocd appset-deploy-rbd 6m16s dr1 Deployed
```

### Starting a dry-run failover test

To test failover for the application `appset-deploy-rbd` in namespace `argocd`,
run the following command:

```console
$ ramenctl failover dry-run --name appset-deploy-rbd --namespace argocd -o dry-run-test
⭐ Using config "config.yaml"
🔎 Starting DRY-RUN failover test

🧪 DRY-RUN MODE: Testing failover to cluster "dr2" without affecting primary
✅ DRY-RUN failover triggered on cluster "dr2"

🔎 Waiting for DRY-RUN to complete (this may take several minutes)
✅ DRY-RUN: Application "appset-deploy-rbd" is available on cluster "dr2"
✅ DRY-RUN: Primary application remains on original cluster

✅ DRY-RUN failover test passed

💡 To abort this dry-run: ramenctl failover dry-run --abort --name appset-deploy-rbd --namespace argocd

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not consistent with other commands - we expect to see:

  • validating config step
  • failover dry run step

The failover step has sub steps to show the progress

There are no extra info lines.

```

The command will:
1. Validate preconditions (not already in dry-run, no active action)
2. Determine the secondary cluster from the DR peer relationship
3. Update the DRPC to trigger dry-run failover
4. Wait for the application to become available on the secondary cluster
5. Generate a test report if `-o` option is provided

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this detailed list. It will be better to describe what the command does.

Regarding a report, all commands generate a report, and it does not depend on the -o argument which is required.


### Checking the test report

If you specified an output directory with `-o`, you can examine the test results:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reports are always generated.


```console
$ tree dry-run-test
dry-run-test
├── failover-dry-run.log
└── failover-dry-run.yaml
```

The YAML report contains the test execution details and timing information.

### Aborting a dry-run failover test

To abort the dry-run test and return the application to its original state:

```console
$ ramenctl failover dry-run --abort --name appset-deploy-rbd --namespace argocd
🔎 Aborting DRY-RUN failover test

⚠️ Aborting dry-run failover for "appset-deploy-rbd"
✅ DRY-RUN aborted

🔎 Waiting for application to return to original state
✅ Application "appset-deploy-rbd" restored to original state

✅ DRY-RUN abort completed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as for the dry-run command.

```

The abort command will:
1. Verify the DRPC is in dry-run mode
2. Read the `last-action` label to determine the original state
3. Restore the DRPC spec to its pre-dry-run configuration
4. Wait for the application to return to the original phase

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description is too low level, this is good for understanding the implementation but not for user documentation.


### How abort restores state

The abort logic uses Ramen's `last-action` label to intelligently restore the
DRPC to its state before the dry-run:

| Original State | last-action label | Restored DRPC Spec |
|----------------|-------------------|-------------------|
| Deployed | `""` (empty) | `action=""`, `failoverCluster=""`, `dryRun=false` |
| FailedOver | `"Failover"` | `action="Failover"`, `failoverCluster=preferredCluster`, `dryRun=false` |
| Relocated | `"Relocate"` | `action="Relocate"`, `failoverCluster=""`, `dryRun=false` |

**Important**: The `last-action` label is NOT updated during dry-run (per Ramen
PR #2416), which allows safe state restoration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good for developers but does not belong to the docs. You move this to the package comment in the code.


## Use cases

### Testing DR readiness

Before a real disaster, test that failover will work:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence is a bit confusing and may be what triggered Nir’s comment. As written, it suggests that users know a disaster is imminent and should test failover before it happens. I think the intended meaning is that users can use this feature to verify disaster recovery readiness by testing whether failover works ahead of time.

Maybe something like:

Users can use this feature to test failover in advance and verify that they are prepared for a real disaster.”

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, updated


```console
# Test failover
$ ramenctl failover dry-run --name my-app --namespace argocd -o test-$(date +%Y%m%d)

# Review results
$ cat test-20260406/failover-dry-run.yaml

# Clean up
$ ramenctl failover dry-run --abort --name my-app --namespace argocd
```

### Periodic DR drills

Schedule regular dry-run tests to ensure DR readiness:

```bash
#!/bin/bash
# Monthly DR drill script

APPS=("app1" "app2" "app3")
NAMESPACE="argocd"
REPORT_DIR="dr-drill-$(date +%Y%m)"

for app in "${APPS[@]}"; do
echo "Testing $app..."
ramenctl failover dry-run --name "$app" --namespace "$NAMESPACE" -o "$REPORT_DIR/$app"
sleep 60 # Wait between tests
ramenctl failover dry-run --abort --name "$app" --namespace "$NAMESPACE"
done
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too risky to automate - it means that you risk your real application failover every month.


### Pre-migration validation

Before performing an actual failover or relocation, verify it will work:

```console
# Test first
$ ramenctl failover dry-run --name critical-app --namespace prod

# If test passes, perform actual failover
$ kubectl patch drpc critical-app -n prod --type merge -p '{"spec":{"action":"Failover","failoverCluster":"dr2"}}'
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not make sense - if you have a real disaster you don't have time for testing.

If you need to relocate, failover test does not make sense, and will cause your relocate to take too much time because failover test causes a split brain.

Dry run failover should certainly NOT used for this use case.


## Troubleshooting

### Error: "DRPC is already in dry-run mode"

You're trying to start a dry-run when one is already active. Abort first:

```console
$ ramenctl failover dry-run --abort --name my-app --namespace argocd
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be an error - if we already in dry run mode we should wait for completion and report success.

For example you may start the operation and then lose connection to the cluster. You should be able to run it again to complete the operation.

The command should be idempotent.


### Error: "DRPC has active action"

The DRPC has an ongoing failover or relocate operation. Wait for it to complete
or cancel it before starting a dry-run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shows we have preconditions to check. This should be listed in the issue.

The fist step in this command should check the preconditions, and fail if any of them are not met.


### Dry-run stuck or taking too long

The command waits up to 10 minutes for completion. If stuck:

1. Check DRPC status:
```console
$ kubectl get drpc my-app -n argocd -o yaml
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command need a --context argument


2. Check Ramen operator logs:
```console
$ kubectl logs -n ramen-system deployment/ramen-hub-operator
```

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command need a --context argument


3. Cancel the operation with Ctrl+C and abort:
```console
$ ramenctl failover dry-run --abort --name my-app --namespace argocd
```

## Safety features

The dry-run failover command includes several safety features:

1. **Precondition validation**: Prevents starting if already in dry-run or has active action
2. **Non-destructive**: Primary application continues running during test
3. **State preservation**: Uses `last-action` label for safe abort
4. **Timeout protection**: 10-minute timeout prevents indefinite hangs
5. **Error detection**: Monitors DRPC conditions for failures
6. **Cancellation support**: Handles Ctrl+C gracefully

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the info here is not needed, and the most important thing is not mentioned, that this command has risk - we create a split brain during the operation, and this will cause a full sync to restore the application protection. We should have a section about the risk of using this command, instead of Safety section that confuses the user about risk-free command.


## Comparison with actual failover

| Feature | Dry-Run Failover | Actual Failover |
|---------|------------------|-----------------|
| Primary app | Keeps running | Stopped |
| Secondary app | Started | Started |
| Data sync | Read-only on secondary | Read-write on secondary |
| Production impact | None | Full failover |
| Reversible | Yes (via abort) | Requires relocate |
| Use case | Testing | Disaster recovery |
| `dryRun` flag | `true` | `false` |
Loading
Loading