Repository navigation
Conversation
PR Reviewer Guide 🔍(Review updated until commit bfffd69)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to bfffd69 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 2695db7
Suggestions up to commit c24d036
|
riysaxen-amzn
left a comment
There was a problem hiding this comment.
Reviewed c24d036 against core's merged RestoreListenerPlugin (OpenSearch#23295) and AlertIndices.kt at the base. The mechanism is right: core calls onRestore on the cluster manager after the cleanup state is published, so the metadata read in repair already has the restored aliases, and the repair is idempotent for the concurrent/duplicate calls the contract allows. Keeping the highest rollover counter is the correct pick since RolloverRequest(index, null) derives the next name from the current write index. Downstream code handles the choice well: retention protects the write index by alias presence (so the detached -1 ages out normally), and updateIndexMapping compares against lastUpdated*HistoryIndex, so the restored index gets put-mapped to the current mapping on the next initialize. Good side effect worth mentioning in the description.
Non-blocking:
- Core's javadoc says delivery is best-effort (a cluster manager failover right after cleanup can drop the call) and that state which must be correct anyway needs a check where the plugin reads it. Today
rolloverIndex.onFailureonly logs "not roll over failed". Running the sameindicesToDetachcheck there (sameis_write_indexguard) would make a missed notification self-heal at the next rollover period. Fine as a follow-up. AlertingRestoreReconciler: core already forks each plugin call onto the generic pool, so theDispatchers.IOscope is a second hop with a scope that is never cancelled, and the comment "so the restore is not held up" describes what core does, not the coroutine. "before any monitor runs" is also not guaranteed; a sweeper run can land in the window between restore completion and alias removal (transient, next run recovers). Either drop the scope or adjust the comment.- Limits to add to the description: (a) same-day same-name collision is a different failure, the restore itself is rejected, and only
rename_patternhelps; (b)include_aliases: falseon the restore avoids the problem with no code and is the only option before 3.10; (c) this needs core 3.10 (@ExperimentalApi, no backport labels on #23295), so it is not backportable; (d) a history index with a failed shard is left out ofrestoredIndicesand is not repaired. - Related Issues is NA; please link one.
The bot's rolloverCounter suggestion cannot apply to these alias names; safe to ignore.
CI: the "Build Alerting" ubuntu failures are the same 52 unit tests in the same classes as main at 18be177, and the run completed 7 more tests with no new failures, so WriteAliasRepairTests passed. Every "Build and test", security-enabled and BWC lane died at Initialize containers on the ECR toomanyrequests quota, so AlertIndicesIT has not run anywhere yet. I have rerun the failed jobs on all four workflows; the security-enabled *IT lane is the one that exercises the plugin identity changing an alias on a system index, so please wait for that before merging.
Approving.
|
|
Persistent review updated to latest commit 2695db7 |
| val request = IndicesAliasesRequest() | ||
| // must_exist=false: a second restore finishing at the same time may already have removed it, so don't fail on that. | ||
| detach.forEach { request.addAliasAction(AliasActions.remove().index(it).alias(alias).mustExist(false)) } | ||
| client.admin().indices().suspendUntil<IndicesAdminClient, AcknowledgedResponse> { aliases(request, it) } |
There was a problem hiding this comment.
The repair runs once and nothing tries again. Core calls onRestore once, on the cluster-manager, after the restore's cleanup task is published. If this request fails, comes back with isAcknowledged == false (the response is thrown away here), or the cluster-manager changes before the callback runs, the alias stays on two indices for good. Rollover then keeps failing on it too.
I reproduced the failure case (see the security comment on line 64): the request was refused, the log shows a single attempt, and 20 s later the alias still named both indices and POST <alias>/_doc still returned 400.
Could the check also run from somewhere that repeats, such as AlertIndices/CommentsIndices onClusterManager or the periodic rollover? Checking isAcknowledged and logging a successful repair would also help operators: today they see only the "Removing it from [...]" warning.
There was a problem hiding this comment.
Thanks.
I'll check isAcknowledged. If it's false, I'll re-read the alias right away rather than wait for the next rollover. If the alias names one index, I'll log success; if it still names two, I'll log an error.
Makes sense to have a fall back for rechecking. I'll also run the same repair from the history rollover in AlertIndices and CommentsIndices, taht way, repair that never ran or failed gets fixed on the next rollover.
| log.warn("Write alias [$alias] names ${aliasedIndices.size} indices and none is the write index. Removing it from $detach.") | ||
| val request = IndicesAliasesRequest() | ||
| // must_exist=false: a second restore finishing at the same time may already have removed it, so don't fail on that. | ||
| detach.forEach { request.addAliasAction(AliasActions.remove().index(it).alias(alias).mustExist(false)) } |
There was a problem hiding this comment.
With the security plugin installed, the plugin identity can be refused this alias removal when the index to detach doesn't match alerting's system index patterns. That happens with rename_pattern, the workaround the PR description recommends.
Repro (-Dsecurity=true -Dhttps=true): snapshot .opendistro-alerting-alert-history-<today>-1, delete it, create the same -1 locally (newer creation date), then restore with "rename_pattern": "(.+)", "rename_replacement": "restored_$1". The counters tie, the newer local index wins, so the repair tries to remove the alias from restored_.opendistro-alerting-alert-history-<today>-1:
WARN WriteAliasRepair Write alias [.opendistro-alerting-alert-history-write] names 2 indices and none is the write index. Removing it from [restored_.opendistro-alerting-alert-history-2026.10.29-1].
ERROR WriteAliasRepair Failed to repair write alias [.opendistro-alerting-alert-history-write] after restoring snapshot [...]
OpenSearchSecurityException[no permissions for [indices:admin/aliases] and User [name=plugin:org.opensearch.alerting.AlertingPlugin, backend_roles=[], requestedTenant=null]]
The alias stays on both indices and writes return 400. The same test without security repairs the alias correctly. The security plugin only lets a plugin identity act on requests that touch nothing but its own registered system indices; restored_... matches none of .opendistro-alerting-alert*, .opensearch-alerting-finding* or .opensearch-alerting-comments*.
There was a problem hiding this comment.
Thanks for catching this. I will drop the suggestion of rename_pattern from the description and add under Limits that a renamed history index isn't repaired. In the docs (another PR I will be sending) for restoring alerting will say not to rename these indices or set include_aliases: false if needed.
| * to the name that sorts last alphabetically, so every repair picks the same index. | ||
| */ | ||
| fun indicesToDetach(aliasedIndices: List<AliasedIndex>): List<String> { | ||
| if (aliasedIndices.size <= 1 || aliasedIndices.any { it.isWriteIndex != null }) return emptyList() |
There was a problem hiding this comment.
any { it.isWriteIndex != null } treats an explicit is_write_index=false as a layout someone chose, but core resolves no write index for [false, null]. The write index is the one with TRUE, or a single index whose flag is unset.
Repro: create ...-000002 with the alias set to {"is_write_index": false}, snapshot it, delete it, create the local -1 with the alias unset, then restore. The repair leaves the alias on both indices and POST <alias>/_doc returns 400 "no write index". Reproduced with and without security.
Alerting never sets the flag itself, so this needs an operator to have set it. Still, == true alone isn't enough as a fix: keeping only the index that has false would still leave no write index, so the repair would also need to set is_write_index: true on the index it keeps (or skip only when some index has true).
There was a problem hiding this comment.
Makes sesne, it will skip whenfalse is set. I'll skip the repair only when an index has is_write_index: true. Also. is_write_index: false is an explicit choice, I won't write to that index. I'll skip the repair only when an index has true, leave indices with false out when choosing which index keeps the alias, and remove the alias from the rest. If every index has false, I'll skip and log an error.
| // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1 | ||
| if (_numNodes > 1) numberOfNodes = _numNodes | ||
| // Snapshot restore tests register a shared file system repository under this path | ||
| setting 'path.repo', "${buildDir}/cluster/shared/repo/integTest" |
There was a problem hiding this comment.
Gradle test clusters already set path.repo to the node's own repo directory, so this line doesn't change anything: the new IT passes with it removed.
The new IT does fail on clusters without path.repo, which is how opensearch-build runs plugin integ tests (./gradlew integTest -Dtests.rest.cluster=... against a distribution; alerting's entry in the test manifest sets no path.repo, unlike index-management and k-NN). With path.repo: [] the test fails at its first step:
repository_exception: [restore-test-repo] location [restore-test-repo] doesn't match any of the locations specified by path.repo because this setting is empty
Options: add path.repo to alerting's additional-cluster-configs in opensearch-build, or have the test skip when the repository can't be registered.
There was a problem hiding this comment.
The line is needed for -PnumNodes=3, each node gets its own repo directory, so fs repository verification fails without it (the IT fails on 3 nodes without the line and passes with it).
I'll keep it and update the comment.
For opensearch-build, that makes sens. For now (to cut for 3.10) I'll make the restore ITs skip when the cluster has no path.repo, and raise a PR there to add path.repo: /tmp to alerting's test config, like index-management and k-NN.
| */ | ||
| class AlertingRestoreReconciler(client: Client, clusterService: ClusterService) { | ||
|
|
||
| private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) |
There was a problem hiding this comment.
Minor: core already calls onRestore on the generic thread pool, and its callPlugin wrapper logs failures with the restore UUID and snapshot. Launching into this scope returns right away, so that handling is skipped: anything thrown outside the per-alias try goes to the thread's uncaught handler instead. The scope also has no exception handler and is never cancelled on node close. Running the repair synchronously in the callback (for example runBlocking) would keep core's error handling and drop the extra scope.
There was a problem hiding this comment.
RIght, will update thanks
Signed-off-by: Sharath Kanaka <sharatcr@amazon.com>
Signed-off-by: Sharath Kanaka <sharatcr@amazon.com>
…dex false Signed-off-by: Sharath Kanaka <sharatcr@amazon.com>
2695db7 to
bfffd69
Compare
|
Persistent review updated to latest commit bfffd69 |
| */ | ||
| fun indicesToDetach(aliasedIndices: List<AliasedIndex>): List<String> { | ||
| if (aliasedIndices.any { it.isWriteIndex == true } || aliasedIndices.all { it.isWriteIndex == false }) return emptyList() | ||
| val keep = aliasedIndices.filter { it.isWriteIndex == null } |
There was a problem hiding this comment.
Nit, non-blocking: when the index left out for is_write_index: false has the higher rollover counter, the index that keeps the alias can't roll over that day.
Repro (same day, with and without security): create <...-{now/d}-000002> with the alias set to {"is_write_index": false}, snapshot it, delete it, create the local <...-{now/d}-1>, then restore. The repair keeps -1 as intended and writes work, but POST <alias>/_rollover targets -000002, which already exists:
resource_already_exists_exception: index [.opendistro-alerting-alert-history-2026.10.30-000002] already exists
It recovers once the date changes, and it needs an operator to have set the flag, so I don't think it should hold the PR. Rollover always names the new index from the write index's own counter, so changing which index keeps the alias can't avoid this. A fix would have to be in the rollover itself, for example passing an explicit new index name one past the highest counter among the history indices. Documenting it under Limits is also fine. The new is_write_index: false IT only checks a write; adding a rollover check there would cover it.
There was a problem hiding this comment.
True that the fix is for the plugin to pick the next rollover name from the existing history indices. For this PR, I'll document it under Limits and add info in the alerting restore docs that this case can make rollover fail until the date changes.
Codecov Report❌ Patch coverage is ❌ Your project check has failed because the head coverage (67.82%) is below the target coverage (70.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #2312 +/- ##
============================================
+ Coverage 67.72% 67.82% +0.09%
- Complexity 105 1815 +1710
============================================
Files 160 170 +10
Lines 10343 14322 +3979
Branches 1522 2090 +568
============================================
+ Hits 7005 9714 +2709
- Misses 2672 3729 +1057
- Partials 666 879 +213 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Description
Why: This is part of supporting system index snapshot restore for alerting, and the third of three alerting PRs for it:
Problem: Alerting writes alert, finding and comment history through a write alias that names one index. If the new cluster runs a monitor before the history indices are restored, it creates its own history index with that alias. Restoring the old cluster's history then brings the alias back on the restored index too, because a restore keeps aliases. The alias now names two indices and neither is marked as the write index, so every write through it fails: findings and completed alerts are not saved, comments fail, and rollover fails. Monitors keep running, so nothing shows the problem.
Fix: Alerting checks each history write alias in two places. Right after a restore, through
onRestorefrom core'sRestoreListenerPlugin(opensearch-project/OpenSearch#23295), it checks the aliases on indices the restore brought back. On every history rollover run (every rollover period, 12 hours by default) it checks them again, so an alias the first check could not fix gets fixed later.When an alias names more than one index and none has
is_write_index: true, alerting keeps the alias only on the index with the highest rollover counter (the restored...-000002over the new...-1), so the next rollover creates...-000003, a name no index has yet. An index withis_write_index: falsenever keeps the alias, because someone marked it as not to be written to. If every index hasfalse, alerting changes nothing and logs an error. The other indices stay searchable, because alerting searches history by index pattern. The repair runs as alerting's plugin identity.Limits:
rename_patternisn't repaired, because alerting can't change an index outside its own patterns.is_write_index: falsehas the higher rollover counter and the same date as the index that keeps the alias, rollover fails that day, because the next name it picks already exists. It works again once the date changes.include_aliases: falseavoids the problem without this fix.Tests: 18 unit tests in
WriteAliasRepairTestsfor choosing the index that keeps the alias, theis_write_indexcases, an alias on one index, an alias outside the repair list, the restore and rollover entry points, and the logged result (including a request that isn't acknowledged). 3 integration tests inAlertIndicesIT: a restore of all three history indices onto a cluster that has its own, after which each alias names only the restored index and a rollover creates...-000003; a restored index withis_write_index: false, after which this cluster's own index keeps the alias and writes through it work; and an alias broken without a restore, which the rollover run repairs. The restore tests skip on a cluster with nopath.repo.Related Issues
Resolves #2313
Check List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.