Skip to content

[Backport release-1.6] fix(opensearch-operator): let a permanently yellow cluster restart - #5074

Merged
myasnikovdaniil merged 5 commits into
release-1.6from
backport/4317-opensearch-operator-release-1.6
Oct 23, 2026
Merged

myasnikovdaniil merged 5 commits into
release-1.6from
backport/4317-opensearch-operator-release-1.6

Conversation

@myasnikovdaniil

Copy link
Copy Markdown
Contributor

What this PR does

Backport of #4317, #4323 and #4341 to release-1.6.

  • Ordered series, picked in merge order, the chainsaw e2e test commit is dropped because release-1.6 has no OpenSearch e2e.
  • Conflicts only in the Dockerfile header comments, the image directory ends up byte-identical to main.
  • On release-1.6 the opensearch chart still turns drainDataNodes on for every release (main limits it to clusters with more than two data nodes, a change that came with feat(opensearch)!: issue HTTP and Dashboards certificates from cert-manager #2682 and is not on this line), so a single-node cluster still cannot finish a rolling restart here. The release note below is adjusted for that.

Screenshots

Not a UI change.

Downstream repositories

Release note

fix(opensearch-operator): a multi-node cluster that stays yellow because of an auto-created index no longer blocks its rolling restart, the operator stops excluding a node whose drain cannot finish, and every call to the cluster's HTTP API now has a timeout. On release-1.6 a single-node cluster still cannot finish a rolling restart, that part needs a chart change that ships with the next minor.

The operator restarts a node only when its health gate opens, and the
gate wanted green, or yellow caused by the observability index alone. A
cluster whose replicas can never be assigned reaches neither: on a single
data node any index with a replica is yellow for good, and the operator
itself turns on the audit log that creates one on the first audited
event. From then on nothing that needs a restart was ever rolled out, a
version upgrade and the move of the HTTP listener onto the cert-manager
chain included.

Upstream rewrote the gate to let a yellow cluster through once allocation
is enabled, every data node has joined and no shard is moving, and to
refuse instead to restart a node that holds the only active copy of a
shard meant to have others. No released operator carries that change, so
it is carried here as a patch against the pinned version, together with
the upstream tests for it.

Backport: the Dockerfile header keeps release-1.6's trace paragraph,
placed after the new one as on main.

Signed-off-by: Aleksei Sviridkin <[email protected]>
Assisted-by: LLM
(cherry picked from commit d9fbeab)
Signed-off-by: Myasnikov Daniil <[email protected]>
…nish

The yellow restart gate carried in the previous patch lets a permanently
yellow cluster through, and with three or more nodes packages/apps/opensearch
turns drainDataNodes on. The restart then reaches PreparePodForDelete, which
excludes the candidate from allocation before it can know whether the drain
will finish. On the cluster that made the gate wait it never finishes: a shard
on that node has nowhere else to go, which is the same condition holding the
cluster yellow. The exclusion is withdrawn only after a pod is really deleted,
so a data node stays out of allocation for as long as the restart is stuck,
shedding its shards onto the rest of the cluster and buying nothing.

Ask the allocator before excluding anything, and only while nothing is moving,
so a drain that is making progress costs nothing. A started shard answering
can_move_to_other_node: no means the drain cannot finish, and then no exclusion
is written and one left by an earlier pass is withdrawn. The two-data-node path
keeps its behaviour, because it never waits for a full drain.

Checking after the fact instead would flap: the next reconcile, ten seconds
later, would exclude the node again.

Upstream as opensearch-project/opensearch-k8s-operator#1585, fixed there by
#1586; the patch goes away with the first release carrying it.

Backport: the Dockerfile header keeps release-1.6's trace paragraph,
placed between the new ones as on main.

Assisted-by: LLM
Signed-off-by: IvanHunters <[email protected]>
(cherry picked from commit 28195d3)
Signed-off-by: Myasnikov Daniil <[email protected]>
… build

The patches under images/opensearch-operator/patches carry their own tests and
nothing runs them. The build compiles main.go, and go-unit-tests in the root
Makefile covers this repository's own Go tree, so a test file that had stopped
compiling still produced an image:

    printf 'func Broken() { this is not go }' >> restart_gate_test.go
    go build -a -o manager main.go   # exit 0

Run go test over the two package trees the patches touch before building the
binary. Both run without a cluster, and the stage takes about eight minutes on
an arm64 builder, most of it the module download the build needs anyway.

Assisted-by: LLM
Signed-off-by: IvanHunters <[email protected]>
(cherry picked from commit 000e2e3)
Signed-off-by: Myasnikov Daniil <[email protected]>
Upstream gives the operator's calls to a cluster no deadline of any kind. The
transport it builds carries no dial, handshake or response-header timeout, no
http.Client is configured, the calls the client makes on its own behalf pass
context.Background(), and the ones that take a context from the caller are
handed one with no deadline on it. A cluster that accepts a connection and then
says nothing holds such a call for as long as the process lives, and with one
worker per controller that single call stops every OpenSearchCluster from being
reconciled. The deletion that then waits out its whole termination timeout is
the visible end of it.

The bound goes on the RoundTripper, the one level every request passes through,
the ones opensearch-go builds for itself included: before a client's first
request the library runs a product check that carries a context.Background() of
its own and decodes the body of the answer, and a client is built per reconcile,
so that check runs on every one. The deadline cannot be released when RoundTrip
returns, because the body goes back to the caller still open; it rides on the
body and is released by Close, the way http.Client applies its own Timeout.

That bounds one attempt. opensearch-go retries a request up to three more times
on 502, 503 and 504 and the transport's deadline starts over on each, so the
calls carry a deadline a second time for the series: through requestContext
where the call decodes the answer before it returns, and through performRequest
where the still-open body goes back to the caller. The dial and the TLS
handshake keep a shorter bound of their own, so an unreachable node fails in a
third of the budget with an error that names what went wrong.

Each of those bounds has a test that fails when it is removed.

Signed-off-by: IvanHunters <[email protected]>
(cherry picked from commit 1a2c876)
Signed-off-by: Myasnikov Daniil <[email protected]>
The timeout tests drive a listener from one goroutine and inspect what it
accepted from another. The plain run passes an unsynchronised access there, so
nothing in this repository would catch one. The builder image carries cgo and
gcc, and the package images are built natively on x86-64 runners, so the
detector is supported where this runs.

Signed-off-by: IvanHunters <[email protected]>
(cherry picked from commit 48058dc)
Signed-off-by: Myasnikov Daniil <[email protected]>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/bug Categorizes issue or PR as related to a bug labels Oct 22, 2026
@myasnikovdaniil
myasnikovdaniil marked this pull request as ready for review October 23, 2026 15:00
@myasnikovdaniil
myasnikovdaniil merged commit c10c1c8 into release-1.6 Oct 23, 2026
14 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the backport/4317-opensearch-operator-release-1.6 branch October 23, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/bug Categorizes issue or PR as related to a bug size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants