Skip to content

[WIP] CNF-24539: T-BC TR ptp4l kill fires HOLDOVER instead of FREERUN - #695

Open
jzding wants to merge 1 commit into
redhat-cne:mainfrom
jzding:kill-ptp4l-tbc
Open

[WIP] CNF-24539: T-BC TR ptp4l kill fires HOLDOVER instead of FREERUN#695
jzding wants to merge 1 commit into
redhat-cne:mainfrom
jzding:kill-ptp4l-tbc

Conversation

@jzding

@jzding jzding commented Jun 8, 2026

Copy link
Copy Markdown
Member

When the TR (time receiver) ptp4l is killed on a T-BC with DPLL hardware, the DPLL stays locked (LHAQ) and T-BC-STATUS never leaves s2. The clock is in holdover, not freerun.

  • processDownEvent: distinguish TR (has phc2sys) from TT via Phc2SysEnabled(). TR fires HOLDOVER + starts holdover timer. TT fires FREERUN (downstream clients lose sync).
  • TR ptp4l kill suppresses OsClockSyncStateChange — DPLL keeps PHC locked, CLOCK_REALTIME stays synced.
  • Add startTBCHoldoverTimer/handleTBCHoldOverState: if ptp4l doesn't recover before timeout, transitions to FREERUN.
  • ParseTBCLogs: cancel holdover timer on HOLDOVER→LOCKED recovery.
  • Tests: TestTBCProcessDownEventTR expects HOLDOVER, new TestTBCProcessDownEventTT expects FREERUN.

When the TR (time receiver) ptp4l is killed on a T-BC with DPLL
hardware, the DPLL stays locked (LHAQ) and T-BC-STATUS never leaves
s2. The clock is in holdover, not freerun.

- processDownEvent: distinguish TR (has phc2sys) from TT via
  Phc2SysEnabled(). TR fires HOLDOVER + starts holdover timer.
  TT fires FREERUN (downstream clients lose sync).
- TR ptp4l kill suppresses OsClockSyncStateChange — DPLL keeps
  PHC locked, CLOCK_REALTIME stays synced.
- Add startTBCHoldoverTimer/handleTBCHoldOverState: if ptp4l
  doesn't recover before timeout, transitions to FREERUN.
- ParseTBCLogs: cancel holdover timer on HOLDOVER→LOCKED recovery.
- Tests: TestTBCProcessDownEventTR expects HOLDOVER, new
  TestTBCProcessDownEventTT expects FREERUN.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Ding <jackding@gmail.com>
@openshift-ci-robot

openshift-ci-robot commented Jun 8, 2026

Copy link
Copy Markdown

@jzding: This pull request references CNF-24539 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

When the TR (time receiver) ptp4l is killed on a T-BC with DPLL hardware, the DPLL stays locked (LHAQ) and T-BC-STATUS never leaves s2. The clock is in holdover, not freerun.

  • processDownEvent: distinguish TR (has phc2sys) from TT via Phc2SysEnabled(). TR fires HOLDOVER + starts holdover timer. TT fires FREERUN (downstream clients lose sync).
  • TR ptp4l kill suppresses OsClockSyncStateChange — DPLL keeps PHC locked, CLOCK_REALTIME stays synced.
  • Add startTBCHoldoverTimer/handleTBCHoldOverState: if ptp4l doesn't recover before timeout, transitions to FREERUN.
  • ParseTBCLogs: cancel holdover timer on HOLDOVER→LOCKED recovery.
  • Tests: TestTBCProcessDownEventTR expects HOLDOVER, new TestTBCProcessDownEventTT expects FREERUN.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested review from aneeshkp and josephdrichard June 8, 2026 15:29
@openshift-ci

openshift-ci Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jzding

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved label Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements T-BC holdover timer management for Telecom Grandmaster clocks. When a T-BC's ptp4l process dies and phc2sys is enabled, a timer starts in HOLDOVER state; if not recovered to LOCKED before timeout, it auto-transitions to FREERUN. Process-down logic now distinguishes TR (phc2sys) from non-TR profiles, suppressing spurious clock sync events for TR.

Changes

T-BC Holdover Timer

Layer / File(s) Summary
Process-Down Event Handler
plugins/ptp_operator/metrics/metrics.go
ExtractMetrics passes configName to processDownEvent; signature updated to accept it. T-BC logic checks phc2sys enablement via LookupPtpProcessOpts; TR profiles emit HOLDOVER and start holdover timer, non-TR emit FREERUN. Early return suppresses OsClockSyncStateChange for TR profiles.
Holdover Timer Implementation
plugins/ptp_operator/metrics/ptp4lParse.go
startTBCHoldoverTimer spawns a goroutine that reads holdover timeout via PtpThreshold. handleTBCHoldOverState waits for timeout or cancellation; on timeout, validates T-BC stats remain HOLDOVER, transitions to FREERUN, publishes PTP state-change event, and updates metrics.
Holdover Recovery Path
plugins/ptp_operator/metrics/logparser.go
ParseTBCLogs cancels holdover timers when T-BC transitions from HOLDOVER to LOCKED by iterating phc2sys-enabled profiles and calling SafeClose on each timer.
Test Coverage
plugins/ptp_operator/metrics/tbc_test.go
TestTBCProcessDownEventTR verifies TR profile transitions to HOLDOVER on ptp4l down, publishes PtpStateChange (not OsClockSyncStateChange), and recovers to LOCKED. TestTBCProcessDownEventTT verifies non-TR profile transitions to FREERUN on ptp4l down.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is directly related to the changeset, providing detailed context about the T-BC TR ptp4l kill behavior fix, the distinction between TR and TT, and the implementation of holdover timer logic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the specific T-BC (Transparent Boundary Clock) behavior change: ptp4l kill now fires HOLDOVER instead of FREERUN for time receiver (TR) profiles, directly matching the main technical change across logparser, metrics, and test files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/ptp_operator/metrics/metrics.go (1)

394-420: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

TR down path can silently skip HOLDOVER + timer and still early-return.

At Line 396, HOLDOVER/timer startup depends on ptpStats[tbcKey] existing with a non-empty alias; at Line 419, the function still returns for TR. If that T-BC stat key isn’t initialized yet, TR ptp4l down emits nothing and no timer is started.

Suggested fix
 if profileType == ptp4lconf.TBC && processName == ptp4lProcessName {
 	tbcKey := types.IFace(stats.TBCMainClockName)
-	if tbcStat, ok := ptpStats[tbcKey]; ok && tbcStat.Alias() != "" {
-		masterResource := fmt.Sprintf("%s/%s", tbcStat.Alias(), MasterClockType)
-		ptpOpts := p.PtpConfigMapUpdates.LookupPtpProcessOpts(profileName)
-		isTR := ptpOpts != nil && ptpOpts.Phc2SysEnabled()
-		if isTR {
-			p.GenPTPEvent(profileName, tbcStat, masterResource, FreeRunOffsetValue, ptp.HOLDOVER, ptp.PtpStateChange)
-			p.startTBCHoldoverTimer(ptpOpts, configName, profileName, tbcStat.Alias())
-		} else {
-			p.GenPTPEvent(profileName, tbcStat, masterResource, FreeRunOffsetValue, ptp.FREERUN, ptp.PtpStateChange)
-		}
-	}
+	ptpStats.CheckSource(tbcKey, configName, ts2phcProcessName)
+	tbcStat := ptpStats[tbcKey]
+	if tbcStat.Alias() == "" {
+		if m, ok := ptpStats[master]; ok && m.Alias() != "" {
+			tbcStat.SetAlias(m.Alias())
+		}
+	}
+	if tbcStat.Alias() != "" {
+		masterResource := fmt.Sprintf("%s/%s", tbcStat.Alias(), MasterClockType)
+		ptpOpts := p.PtpConfigMapUpdates.LookupPtpProcessOpts(profileName)
+		isTR := ptpOpts != nil && ptpOpts.Phc2SysEnabled()
+		if isTR {
+			p.GenPTPEvent(profileName, tbcStat, masterResource, FreeRunOffsetValue, ptp.HOLDOVER, ptp.PtpStateChange)
+			p.startTBCHoldoverTimer(ptpOpts, configName, profileName, tbcStat.Alias())
+		} else {
+			p.GenPTPEvent(profileName, tbcStat, masterResource, FreeRunOffsetValue, ptp.FREERUN, ptp.PtpStateChange)
+		}
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ptp_operator/metrics/metrics.go` around lines 394 - 420, The TR
early-return is happening unconditionally for profileType == ptp4lconf.TBC which
can skip the tbcKey branch that should start HOLDOVER and startTBCHoldoverTimer;
change the control flow so you lookup ptpOpts (via
p.PtpConfigMapUpdates.LookupPtpProcessOpts(profileName)) and only perform an
early return after you have attempted the tbcKey handling — i.e., ensure the
existing tbcKey block (using tbcKey := types.IFace(stats.TBCMainClockName),
ptpStats[tbcKey], p.GenPTPEvent(..., ptp.HOLDOVER...), and
p.startTBCHoldoverTimer(...)) always runs before you return for TR, or
alternatively move the ptpOpts.Phc2SysEnabled() check earlier so you can invoke
the HOLDOVER + startTBCHoldoverTimer when tbcStat exists and only return if TR
has been handled (do not return silently when tbcStat/alias was missing).
🧹 Nitpick comments (1)
plugins/ptp_operator/metrics/tbc_test.go (1)

405-471: ⚡ Quick win

TR test currently bypasses the new timer path.

Line 407 enables mock mode, and startTBCHoldoverTimer short-circuits in mock mode, so this test does not verify auto-expire-to-FREERUN or cancellation-on-LOCKED recovery. Add a non-mock unit with a short timeout (or direct handleTBCHoldOverState invocation) to cover the new timer behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ptp_operator/metrics/tbc_test.go` around lines 405 - 471,
TestTBCProcessDownEventTR currently sets eventManager.MockTest(true) which
causes startTBCHoldoverTimer to short-circuit and skip the new timer path;
change the test to exercise the timer by either creating a non-mock eventManager
(MockTest(false) or remove the mock call) and configuring a short holdover
timeout before calling startTBCHoldoverTimer so the timer will auto-expire to
FREERUN, or keep the mock but explicitly invoke handleTBCHoldOverState with the
tbcKey after a small sleep to simulate timer expiry; ensure assertions check for
FREERUN on expiry and that a subsequent ParseTBCLogs/LOCKED cancels the timer
and returns to LOCKED (referencing TestTBCProcessDownEventTR, eventManager,
startTBCHoldoverTimer, handleTBCHoldOverState, and tbcKey).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/ptp_operator/metrics/logparser.go`:
- Around line 496-505: The current loop over p.PtpConfigMapUpdates.TBCProfiles
cancels the first found TR event threshold (via
LookupEventThreshold(...).SafeClose()) rather than the threshold for the
specific recovering profile, risking cancelling the wrong timer; change the
logic to target only the recovering profile instead of iterating all TBCProfiles
— i.e., use the recovering profile identifier (the profile that just entered
LOCKED recovery) and call
p.PtpConfigMapUpdates.LookupPtpProcessOpts(recoveringProfile).Phc2SysEnabled()
and p.PtpConfigMapUpdates.LookupEventThreshold(recoveringProfile) and, if
non-nil, call SafeClose() on that threshold; keep use of LookupPtpProcessOpts,
Phc2SysEnabled, LookupEventThreshold and SafeClose to locate and close only the
matching timer.

---

Outside diff comments:
In `@plugins/ptp_operator/metrics/metrics.go`:
- Around line 394-420: The TR early-return is happening unconditionally for
profileType == ptp4lconf.TBC which can skip the tbcKey branch that should start
HOLDOVER and startTBCHoldoverTimer; change the control flow so you lookup
ptpOpts (via p.PtpConfigMapUpdates.LookupPtpProcessOpts(profileName)) and only
perform an early return after you have attempted the tbcKey handling — i.e.,
ensure the existing tbcKey block (using tbcKey :=
types.IFace(stats.TBCMainClockName), ptpStats[tbcKey], p.GenPTPEvent(...,
ptp.HOLDOVER...), and p.startTBCHoldoverTimer(...)) always runs before you
return for TR, or alternatively move the ptpOpts.Phc2SysEnabled() check earlier
so you can invoke the HOLDOVER + startTBCHoldoverTimer when tbcStat exists and
only return if TR has been handled (do not return silently when tbcStat/alias
was missing).

---

Nitpick comments:
In `@plugins/ptp_operator/metrics/tbc_test.go`:
- Around line 405-471: TestTBCProcessDownEventTR currently sets
eventManager.MockTest(true) which causes startTBCHoldoverTimer to short-circuit
and skip the new timer path; change the test to exercise the timer by either
creating a non-mock eventManager (MockTest(false) or remove the mock call) and
configuring a short holdover timeout before calling startTBCHoldoverTimer so the
timer will auto-expire to FREERUN, or keep the mock but explicitly invoke
handleTBCHoldOverState with the tbcKey after a small sleep to simulate timer
expiry; ensure assertions check for FREERUN on expiry and that a subsequent
ParseTBCLogs/LOCKED cancels the timer and returns to LOCKED (referencing
TestTBCProcessDownEventTR, eventManager, startTBCHoldoverTimer,
handleTBCHoldOverState, and tbcKey).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 1c0f7f98-fbf9-4d7b-86e2-0faf0fa3099f

📥 Commits

Reviewing files that changed from the base of the PR and between ef79857 and 7cef993.

📒 Files selected for processing (4)
  • plugins/ptp_operator/metrics/logparser.go
  • plugins/ptp_operator/metrics/metrics.go
  • plugins/ptp_operator/metrics/ptp4lParse.go
  • plugins/ptp_operator/metrics/tbc_test.go

Comment on lines +496 to +505
for _, tbcProfile := range p.PtpConfigMapUpdates.TBCProfiles {
ptpOpts := p.PtpConfigMapUpdates.LookupPtpProcessOpts(tbcProfile)
if ptpOpts != nil && ptpOpts.Phc2SysEnabled() {
if t := p.PtpConfigMapUpdates.LookupEventThreshold(tbcProfile); t != nil {
log.Infof("T-BC: cancelling holdover timer on LOCKED recovery: profile=%s", tbcProfile)
t.SafeClose()
}
break
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Timer cancellation is not scoped to the recovering profile.

At Line 496, cancellation iterates all TBCProfiles and closes the first TR threshold found. On nodes with multiple TR T-BC profiles, this can cancel the wrong timer and let the real one expire into a false FREERUN.

Suggested fix
 if lastClockState == ptp.HOLDOVER && clockState.State == ptp.LOCKED {
-	for _, tbcProfile := range p.PtpConfigMapUpdates.TBCProfiles {
-		ptpOpts := p.PtpConfigMapUpdates.LookupPtpProcessOpts(tbcProfile)
-		if ptpOpts != nil && ptpOpts.Phc2SysEnabled() {
-			if t := p.PtpConfigMapUpdates.LookupEventThreshold(tbcProfile); t != nil {
-				log.Infof("T-BC: cancelling holdover timer on LOCKED recovery: profile=%s", tbcProfile)
-				t.SafeClose()
-			}
-			break
-		}
-	}
+	cfg := p.GetPTPConfig(types.ConfigName(configName))
+	if cfg != nil {
+		tbcProfile := cfg.Profile
+		if ptpOpts := p.PtpConfigMapUpdates.LookupPtpProcessOpts(tbcProfile); ptpOpts != nil && ptpOpts.Phc2SysEnabled() {
+			if t := p.PtpConfigMapUpdates.LookupEventThreshold(tbcProfile); t != nil {
+				log.Infof("T-BC: cancelling holdover timer on LOCKED recovery: profile=%s", tbcProfile)
+				t.SafeClose()
+			}
+		}
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/ptp_operator/metrics/logparser.go` around lines 496 - 505, The
current loop over p.PtpConfigMapUpdates.TBCProfiles cancels the first found TR
event threshold (via LookupEventThreshold(...).SafeClose()) rather than the
threshold for the specific recovering profile, risking cancelling the wrong
timer; change the logic to target only the recovering profile instead of
iterating all TBCProfiles — i.e., use the recovering profile identifier (the
profile that just entered LOCKED recovery) and call
p.PtpConfigMapUpdates.LookupPtpProcessOpts(recoveringProfile).Phc2SysEnabled()
and p.PtpConfigMapUpdates.LookupEventThreshold(recoveringProfile) and, if
non-nil, call SafeClose() on that threshold; keep use of LookupPtpProcessOpts,
Phc2SysEnabled, LookupEventThreshold and SafeClose to locate and close only the
matching timer.

@jzding jzding changed the title CNF-24539: T-BC TR ptp4l kill fires HOLDOVER instead of FREERUN [WIP] CNF-24539: T-BC TR ptp4l kill fires HOLDOVER instead of FREERUN Jun 8, 2026
@openshift-ci

openshift-ci Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants