-
Notifications
You must be signed in to change notification settings - Fork 23
Add dry-run failover commands #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
| } |
| 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) | ||
|
|
||
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation suggests that the right user interface is: |
||
|
|
||
| ### 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not consistent with other commands - we expect to see:
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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.”
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"}}' | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ``` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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` | | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ramenctl failover dry-runandramenctl failover dry-run --abortdo not make sense.I think this should be an a separate command like
failover-test, or an option for the failover command.