Skip to content

[9.4] (backport #15423) Give service-runtime components more time to check in before failing an upgrade - #15863

Merged
macdewee merged 1 commit into
9.4from
mergify/bp/9.4/pr-15423
Jul 28, 2026
Merged

macdewee merged 1 commit into
9.4from
mergify/bp/9.4/pr-15423

Conversation

@mergify

@mergify mergify Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Changes check-in behavior for the services we don't have control over, like Elastic Defend. Computes the number of missed check-ins to allow based on their own startup time as much as possible, limited by the upgrade watcher's grace period with some margin, so we do not fail prematurely. Always uses the live-reloaded grace period configuration, so a config change takes effect without restarting Agent.

Why is it important?

Elastic Defend sometimes takes longer than 90 seconds to fully restart after an Agent upgrade, even when nothing is wrong. Agent was treating that normal slow restart as a broken upgrade and automatically rolling it back. This caused unnecessary rollbacks, and customers were working around it by removing Defend before upgrading and re-adding it afterward.

Waiting longer only helps if a truly broken component still gets caught before the upgrade watcher gives up. That's why the wait is capped below the watcher's grace period, and why that cap updates live if the grace period setting changes.

Checklist

  • I have read and understood the pull request guidelines of this project.
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works
  • I have added an entry in ./changelog/fragments using the changelog tool
  • I have added an integration test or an E2E test

Disruptive User Impact

None expected. Service-managed components (currently only Elastic Defend) get more time before being marked failed, but only up to their own configured operation timeouts and always below the upgrade watcher's grace period. Components that are genuinely stuck still end up failed, just later. Changing the grace period setting now takes effect immediately, without restarting Agent.

How to test this PR locally

Run go test ./pkg/component/runtime/... -run "TestService|TestManagerReload" -v to see the new unit tests covering: a normal check-in staying healthy, a slow-but-within-timeout check-in staying degraded instead of failed, a genuinely stuck component still ending up failed, a component with no configured timeout keeping the old 90 second behavior, the wait staying capped below the grace period, and the grace period updating live on a config reload.

Related issues

  • Relates elastic/ingest-dev#6395
  • Relates elastic/sdh-beats#6510
  • Relates elastic/sdh-beats#7341

This is an automatic backport of pull request #15423 done by [Mergify](https://mergify.com).

…an upgrade (#15423)

* Give service-runtime components more time to check in before failing an upgrade

Elastic Defend restarting slowly after an upgrade could be mistaken for a
broken Agent build and trigger an unnecessary rollback. The FAILED
threshold for service-runtime components is now derived from their own
configured install/uninstall/check timeouts instead of the generic 90s
window; DEGRADED reporting cadence is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Cap service check-in failure window to a safe margin below the upgrade watcher grace period

A service-runtime component's failure threshold could still exceed a very
short configured grace period, and the grace period value was captured once
at startup so a live config reload never reached already-running components.
Cap the threshold relative to the grace period and share it live between the
Manager and its components so a reload takes effect immediately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Simplify grace period wiring: drop ManagerOption in favor of a plain setter

The grace period is a mutable, post-construction value now that Reload can
update it live, so threading it through a functional-options mechanism at
construction time added an abstraction the value did not need. A plain
SetServiceCheckinGracePeriod method called once after NewManager achieves
the same result with less API surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Declare Reload on the RuntimeManager interface instead of type-asserting

RuntimeManager was the only reloadable subsystem in Coordinator.generateAST
without a Reload method on its own interface, so reloading it required a
type assertion to the concrete *runtime.Manager. Add Reload to the interface
itself, matching UpgradeManager and MonitorManager, and give the test fake a
no-op implementation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Trim comments that leaked implementation detail

Several comments walked through how the grace period sharing/atomics work
instead of just stating what each piece is for. Shortened them to plain,
purpose-focused descriptions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Drop remaining method-name reference from a comment

The field comment still named Reload as the update mechanism, which the
gracePeriodValue type comment already covers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Drop redundant live-update note from a field comment

The gracePeriodValue type comment already explains that the value can be
updated live, so restating it on every field that holds one was redundant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add Reload to the generated RuntimeManager mock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Regenerate RuntimeManager mock via mockery and add it to .mockery.yaml

Adds the coordinator's RuntimeManager interface to .mockery.yaml so it
is regenerated automatically going forward. Runs mockery v3 to produce
the updated mock (includes the new Reload method) and picks up minor
v3-format header updates across other mocks in the same run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Pin service check-in grace period to upgrade marker during active upgrade

Stores the configured watcher grace period in the upgrade marker when an
upgrade starts. The new agent reads it back and uses it as a ceiling on
the check-in miss cap, so a Fleet config reload that increases the grace
period mid-upgrade cannot push component failure detection past the
deadline the watcher already committed to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Assign upgradeGracePeriod to a variable in NewUpgrader

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Simplify grace period handling: plain field on Manager, skip Reload during upgrade

gracePeriodValue goes back to a simple atomic with no ceiling. Manager
gains a plain upgradeGracePeriod field that is set once from the marker
at startup. Reload skips updating the grace period while an upgrade is
active — the marker value is a constant for this upgrade and live config
changes are irrelevant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Lift upgrade grace period freeze when upgrade reaches terminal state

Elastic Defend can take longer than the hardcoded ~90 s window to restart,
causing the upgrade watcher to roll back a healthy upgrade. This commit
introduces a grace-period freeze so the runtime uses the same window
configured in the upgrade watcher for the duration of an upgrade.

Changes:
- gracePeriodValue on Manager holds both a live value (updated by Reload)
  and an upgrade value (frozen when an upgrade starts, cleared on
  completion). Get() returns the upgrade value while non-zero.
- IsUpgradeActive() on UpdateMarker encapsulates the three conditions
  (not acked, GracePeriod > 0, non-terminal) used at startup to decide
  whether to activate the freeze.
- Coordinator clears the freeze via ClearUpgradeGracePeriod when the
  upgradeMarkerUpdate channel delivers a terminal or nil-Details marker.
- MockRuntimeManager regenerated to include ClearUpgradeGracePeriod.
- Tests: TestManagerGracePeriodUpgradeFreeze, TestUpdateMarkerIsUpgradeActive,
  TestCoordinatorClearUpgradeGracePeriodOnMarkerUpdate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove upgrade marker grace period freeze mechanism

The freeze mechanism (snapshot GracePeriod in the marker, freeze the service
check-in window during active upgrades) was added to prevent a config reload
from shortening the window mid-upgrade. In practice the grace period is never
user-configurable at runtime, so the problem it solved does not exist.

Remove: UpdateMarker.GracePeriod field and serializer counterpart,
IsUpgradeActive(), gracePeriodValue type and its atomic fields, the three
Manager methods (SetServiceCheckinGracePeriod, SetUpgradeGracePeriod,
ClearUpgradeGracePeriod), ClearUpgradeGracePeriod from RuntimeManager
interface, the coordinator handler that called it, and all associated tests.

Simplify: maxCheckinMisses no longer takes a dynamic cap from the manager;
checkinFailureTimeout drops the uninstall operation (only install/check
determine the initial startup window). Bump defaultGracePeriodDuration to
11m so it always exceeds the 10m Elastic Defend operation timeout, and add
TestUpgradeGracePeriodExceedsMaxServiceTimeout to enforce that relationship.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Drop Reload from RuntimeManager; strengthen grace period constraint test

Remove the no-op Reload method from RuntimeManager interface, Manager,
the mock, and fakeRuntimeManager — it was left over from the abandoned
freeze mechanism and never needed.

Make TestUpgradeGracePeriodExceedsMaxServiceTimeout load the real
endpoint-security.spec.yml so a spec change that breaks the grace period
invariant fails the test rather than passing against a hardcoded constant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit eb3502f)
@mergify mergify Bot added the backport label Jul 28, 2026
@mergify
mergify Bot requested a review from a team as a code owner July 28, 2026 12:13
@mergify
mergify Bot requested review from lorienhu and samuelvl and removed request for a team July 28, 2026 12:13
@mergify mergify Bot added the backport label Jul 28, 2026
@github-actions github-actions Bot added the Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team label Jul 28, 2026
@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/elastic-agent-control-plane (Team:Elastic-Agent-Control-Plane)

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

💚 Build Succeeded

cc @macdewee

@macdewee
macdewee enabled auto-merge (squash) July 28, 2026 14:47
@macdewee
macdewee merged commit af5e856 into 9.4 Jul 28, 2026
26 checks passed
@macdewee
macdewee deleted the mergify/bp/9.4/pr-15423 branch July 28, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport Team:Elastic-Agent-Control-Plane Label for the Agent Control Plane team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant