From 5e209d4ffbd9d7f9cb4495d57fa0164a214f9ecd Mon Sep 17 00:00:00 2001 From: Jim Ramsay Date: Tue, 30 Jun 2026 18:48:14 -0400 Subject: [PATCH] Close T-GM and T-BC phc2sys clock synchronization race conditions Synchronizing the system clock too early can lead to large time offsets that settle slowly. By delaying phc2sys from running until the PHC is "reasonably synchronized" (ie within 1.0s offset of a trusted timesource), we ensure small offsets only which converge quickly. For OC and BC, this means delaying phc2sys until ptp4l offset is <1.0s (and in the case of mutli-profile BC, choosing the proper ptp4l to evaluate is key). In GM, we delay phc2sys until ts2phc offset is <1.0s. Cherry-picked from OCPBUGS-85586_phc2sys_ts2phc_race-4.22 (d2ff2d15) and adapted for the 4.21 codebase (no hardwareconfig, alias, or v2alpha1 APIs). Fixes: OCPBUGS-85586 Assisted-by: Claude Opus 4.6 and pi.dev Signed-off-by: Jim Ramsay --- pkg/daemon/daemon.go | 85 +++++- pkg/daemon/daemon_internal_test.go | 340 +++++++++++++++++++++++- pkg/daemon/log_parsing.go | 6 + pkg/daemon/ready.go | 9 + pkg/daemon/testdata/profile-tbc-tr.yaml | 41 +-- pkg/daemon/testdata/profile-tbc-tt.yaml | 1 + pkg/daemon/testdata/profile-tgm.yaml | 237 +++++++++++++++++ 7 files changed, 690 insertions(+), 29 deletions(-) create mode 100644 pkg/daemon/testdata/profile-tgm.yaml diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go index 8b4a83e55..bdd5f3b47 100644 --- a/pkg/daemon/daemon.go +++ b/pkg/daemon/daemon.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -108,6 +109,17 @@ type ProcessManager struct { ptpEventHandler *event.EventHandler } +// findProcessesByName returns a list of processes with the given name +func (p *ProcessManager) findProcessesByName(name string) []*ptpProcess { + var procs []*ptpProcess + for _, proc := range p.process { + if proc != nil && proc.name == name { + procs = append(procs, proc) + } + } + return procs +} + // NewProcessManager is used by unit tests func NewProcessManager() *ProcessManager { processPTP := &ptpProcess{} @@ -251,6 +263,7 @@ type ptpProcess struct { dn *Daemon cmdSetEnabledMutex sync.Mutex offset float64 + skipInitialStartup string } func (p *ptpProcess) Stopped() bool { @@ -303,6 +316,9 @@ type Daemon struct { // Allow vendors to include plugins pluginManager plugin.PluginManager + + delayedPhc2sys atomic.Bool + delayedPhc2sysMu sync.Mutex // protects skipInitialStartup on phc2sys processes } // New LinuxPTP is called by daemon to generate new linuxptp instance @@ -467,9 +483,7 @@ func (dn *Daemon) applyNodePTPProfiles() error { if p != nil { p.eventCh = dn.processManager.eventChannel // start ptp4l process early , it doesn't have - if p.depProcess == nil { - go p.cmdRun(dn.stdoutToSocket, &dn.pluginManager) - } else { + if p.depProcess != nil { for _, d := range p.depProcess { if d != nil { time.Sleep(3 * time.Second) @@ -491,11 +505,24 @@ func (dn *Daemon) applyNodePTPProfiles() error { glog.Infof("enabling dep process %s with Max %d Min %d Holdover %d", d.Name(), p.ptpClockThreshold.MaxOffsetThreshold, p.ptpClockThreshold.MinOffsetThreshold, p.ptpClockThreshold.HoldOverTimeout) } } - go p.cmdRun(dn.stdoutToSocket, &dn.pluginManager) } + if p.skipInitialStartup != "" { + glog.Infof("Delaying %s startup: %s", p.name, p.skipInitialStartup) + continue + } + go p.cmdRun(dn.stdoutToSocket, &dn.pluginManager) dn.pluginManager.AfterRunPTPCommand(&p.nodeProfile, p.name) } } + // Arm the delayed-phc2sys flag now that the startup loop is complete. + // Keeping it false during the loop ensures HandleDelayedPhc2sysStartup + // cannot clear skipInitialStartup and race with the loop's skip check. + for _, p := range dn.processManager.process { + if p != nil && p.skipInitialStartup != "" { + dn.delayedPhc2sys.Store(true) + break + } + } dn.pluginManager.PopulateHwConfig(dn.hwconfigs) *dn.refreshNodePtpDevice = true dn.readyTracker.setConfig(true) @@ -792,6 +819,13 @@ func (dn *Daemon) applyNodePtpProfile(runID int, nodeProfile *ptpv1.PtpProfile) // TODO addScheduling dprocess.depProcess = append(dprocess.depProcess, pmcProcess) } + } else if pProcess == phc2sysProcessName { + glog.Infof("Setting up phc2sys (%s)", clockType) + // Delay phc2sys startup until the clock source has synchronized. + dn.delayedPhc2sysMu.Lock() + dprocess.skipInitialStartup = "waiting for PHC synchronization before adjusting system time" + dn.delayedPhc2sysMu.Unlock() + glog.Infof("Delaying phc2sys startup: %s", dprocess.skipInitialStartup) } else if pProcess == ts2phcProcessName { //& if the x plugin is enabled if clockType == event.GM { if output.gnss_serial_port == "" { @@ -1302,6 +1336,46 @@ func (p *ptpProcess) MonitorEvent(offset float64, clockState string) { // not implemented } +// HandleDelayedPhc2sysStartup checks if phc2sys was delayed and if the current offset is within the 1s threshold, starts it. +func (dn *Daemon) HandleDelayedPhc2sysStartup(source string, offset float64, profileName *string) { + if profileName == nil { + return + } + if !dn.delayedPhc2sys.Load() { + return + } + if math.Abs(offset) < 1000000000 { + dn.delayedPhc2sysMu.Lock() + defer dn.delayedPhc2sysMu.Unlock() + if !dn.delayedPhc2sys.Load() { // re-check under lock + return + } + for _, proc := range dn.processManager.findProcessesByName(phc2sysProcessName) { + if proc.skipInitialStartup == "" || proc.nodeProfile.Name == nil { + continue + } + // Match if the reporting process is in the same profile as phc2sys, + // or in one of phc2sys's HA-linked profiles. The latter handles the + // case where phc2sys is in a dedicated profile with no ptp4l of its + // own (e.g. test-dual-nic-bc-ha with haProfiles=master1,master2). + _, linkedByHA := proc.haProfile[*profileName] + if *proc.nodeProfile.Name == *profileName || linkedByHA { + glog.Infof("%s offset is %f (sub-second); enabling %s", source, offset, proc.name) + proc.skipInitialStartup = "" + proc.cmdSetEnabled(true) + dn.pluginManager.AfterRunPTPCommand(&proc.nodeProfile, proc.name) + } + } + // Only clear the daemon-wide flag once no phc2sys processes remain delayed. + for _, proc := range dn.processManager.findProcessesByName(phc2sysProcessName) { + if proc.skipInitialStartup != "" { + return + } + } + dn.delayedPhc2sys.Store(false) + } +} + func (p *ptpProcess) ProcessTs2PhcEvents(ptpOffset float64, source string, iface string, state event.PTPState, extraValue map[event.ValueType]interface{}) { var ptpState event.PTPState ptpState = state @@ -1313,6 +1387,9 @@ func (p *ptpProcess) ProcessTs2PhcEvents(ptpOffset float64, source string, iface } if source == ts2phcProcessName { // for ts2phc send it to event to create metrics and events + if p.dn != nil { + p.dn.HandleDelayedPhc2sysStartup(source, ptpOffset, p.nodeProfile.Name) + } var values = make(map[event.ValueType]interface{}) values[event.OFFSET] = ptpOffsetInt64 diff --git a/pkg/daemon/daemon_internal_test.go b/pkg/daemon/daemon_internal_test.go index 76bae6fe3..ba8560bf3 100644 --- a/pkg/daemon/daemon_internal_test.go +++ b/pkg/daemon/daemon_internal_test.go @@ -5,6 +5,7 @@ package daemon import ( "os" "strings" + "sync" "testing" "time" @@ -16,6 +17,10 @@ import ( "sigs.k8s.io/yaml" ) +const ( + testSkipStartupReason = "delayed" +) + func loadProfile(path string) (*ptpv1.PtpProfile, error) { profileData, err := os.ReadFile(path) if err != nil { @@ -110,9 +115,19 @@ func Test_applyProfile_synce(t *testing.T) { func Test_applyProfile_TBC(t *testing.T) { defer clean(t) - testDataFiles := []string{ - "testdata/profile-tbc-tt.yaml", - "testdata/profile-tbc-tr.yaml", + + tests := []struct { + dataFile string + expectedProcesses []string + }{ + { + dataFile: "testdata/profile-tbc-tt.yaml", + expectedProcesses: []string{ptp4lProcessName}, + }, + { + dataFile: "testdata/profile-tbc-tr.yaml", + expectedProcesses: []string{ptp4lProcessName, ptp4lProcessName, ts2phcProcessName, phc2sysProcessName}, + }, } stopCh := make(<-chan struct{}) assert.NoError(t, leap.MockLeapFile()) @@ -141,17 +156,101 @@ func Test_applyProfile_TBC(t *testing.T) { ) assert.NotNil(t, dn) - for i := range len(testDataFiles) { + for _, test := range tests { mkPath(t) - profile, err := loadProfile(testDataFiles[i]) + profile, err := loadProfile(test.dataFile) assert.NoError(t, err) // Will assert inside in case of error: err = dn.applyNodePtpProfile(0, profile) assert.NoError(t, err) + + // Ensure for T-BC that phc2sys is selected for delayed-start + actualProcesses := []string{} + for _, p := range dn.processManager.process { + actualProcesses = append(actualProcesses, p.name) + if p.name == phc2sysProcessName { + assert.NotEmpty(t, p.skipInitialStartup, "Ensure phc2sys is startup-delayed for T-BC") + } + } + assert.ElementsMatch(t, test.expectedProcesses, actualProcesses, "Ensure T-BC has the required processes prepared (%s)", test.dataFile) clean(t) } } +func Test_applyProfile_TGM(t *testing.T) { + defer clean(t) + mkPath(t) + + stopCh := make(<-chan struct{}) + assert.NoError(t, leap.MockLeapFile()) + defer func() { + close(leap.LeapMgr.Close) + time.Sleep(100 * time.Millisecond) + assert.Nil(t, leap.LeapMgr) + }() + dn := New( + "test-node-name", + "openshift-ptp", + false, + nil, + &LinuxPTPConfUpdate{ + UpdateCh: make(chan bool), + NodeProfiles: []ptpv1.PtpProfile{}, + }, + stopCh, + []string{"e810"}, + &[]ptpv1.HwConfig{}, + nil, + make(chan bool), + 30, + &ReadyTracker{}, + ) + assert.NotNil(t, dn) + + profile, err := loadProfile("testdata/profile-tgm.yaml") + assert.NoError(t, err) + + err = dn.applyNodePtpProfile(0, profile) + assert.NoError(t, err) + + var ts2phcProc *ptpProcess + var ptp4lProc *ptpProcess + for _, p := range dn.processManager.process { + switch p.name { + case ts2phcProcessName: + ts2phcProc = p + case ptp4lProcessName: + ptp4lProc = p + case phc2sysProcessName: + assert.NotEmpty(t, p.skipInitialStartup, "Ensure phc2sys is startup-delayed for T-GM") + } + } + + // 1. ts2phc must have both gpsd and gpspipe as dependent processes. + if assert.NotNil(t, ts2phcProc, "ts2phc process should exist for T-GM profile") { + depNames := make([]string, len(ts2phcProc.depProcess)) + for i, d := range ts2phcProc.depProcess { + depNames[i] = d.Name() + } + assert.Contains(t, depNames, GPSD_PROCESSNAME, "gpsd must be a dependent process of ts2phc for T-GM") + assert.Contains(t, depNames, GPSPIPE_PROCESSNAME, "gpspipe must be a dependent process of ts2phc for T-GM") + } + + // 2. ptp4l should NOT have a PMC dependent process for GM profiles. + if assert.NotNil(t, ptp4lProc, "ptp4l process should exist for T-GM profile") { + for _, d := range ptp4lProc.depProcess { + assert.NotEqual(t, "pmc", d.Name(), "ptp4l should not have PMC as a dependent process for T-GM") + } + } + + // 3. ts2phc opts must include holdover and servo parameters for GM. + // These are auto-appended by applyNodePtpProfile for GM clock types. + ts2phcOpts := *profile.Ts2PhcOpts + assert.Contains(t, ts2phcOpts, "--ts2phc.holdover", "ts2phc opts must include holdover timeout for T-GM") + assert.Contains(t, ts2phcOpts, "--servo_offset_threshold", "ts2phc opts must include servo offset threshold for T-GM") + assert.Contains(t, ts2phcOpts, "--servo_num_offset_values 10", "ts2phc opts must include servo num offset values for T-GM") +} + func TestGetPTPClockId_ValidInput(t *testing.T) { p := &ptpProcess{ nodeProfile: ptpv1.PtpProfile{ @@ -663,3 +762,234 @@ func TestEmitClockClassLogs_EmitsWithNilParentDS(t *testing.T) { pm.EmitClockClassLogs() }, "EmitClockClassLogs should not panic with nil parentDS") } + +// --- ReadyTracker.Ready() unit tests --- + +func makeReadyTracker(processes []*ptpProcess) *ReadyTracker { + return &ReadyTracker{ + config: true, + processManager: &ProcessManager{ + process: processes, + }, + } +} + +func TestReady_NoProcesses(t *testing.T) { + rt := makeReadyTracker(nil) + ok, msg := rt.Ready() + assert.False(t, ok) + assert.Contains(t, msg, "No processes") +} + +func TestReady_AllRunningWithMetrics(t *testing.T) { + rt := makeReadyTracker([]*ptpProcess{ + {name: ptp4lProcessName, stopped: false, hasCollectedMetrics: true}, + {name: phc2sysProcessName, stopped: false, hasCollectedMetrics: true}, + }) + ok, msg := rt.Ready() + assert.True(t, ok, msg) +} + +func TestReady_StoppedProcessReportsNotReady(t *testing.T) { + rt := makeReadyTracker([]*ptpProcess{ + {name: ptp4lProcessName, stopped: false, hasCollectedMetrics: true}, + {name: phc2sysProcessName, stopped: true}, + }) + ok, msg := rt.Ready() + assert.False(t, ok) + assert.Contains(t, msg, "Stopped") + assert.Contains(t, msg, phc2sysProcessName) +} + +func TestReady_DelayedPhc2sysNotReportedAsStopped(t *testing.T) { + // phc2sys is intentionally delayed (skipInitialStartup set): the pod + // should be considered ready without it. + rt := makeReadyTracker([]*ptpProcess{ + {name: ptp4lProcessName, stopped: false, hasCollectedMetrics: true}, + {name: phc2sysProcessName, stopped: true, skipInitialStartup: testSkipStartupReason}, + }) + ok, msg := rt.Ready() + assert.True(t, ok, msg) +} + +func TestReady_NilProcessEntrySkipped(t *testing.T) { + // nil slots in the process slice must not panic. + rt := makeReadyTracker([]*ptpProcess{ + {name: ptp4lProcessName, stopped: false, hasCollectedMetrics: true}, + nil, + }) + ok, msg := rt.Ready() + assert.True(t, ok, msg) +} + +func TestReady_AllProcessesDelayed(t *testing.T) { + // If every process has skipInitialStartup set (e.g. a phc2sys-only HA profile + // where ptp4l lives in separate profiles), the pod must not report ready. + rt := makeReadyTracker([]*ptpProcess{ + {name: phc2sysProcessName, stopped: true, skipInitialStartup: testSkipStartupReason}, + }) + ok, msg := rt.Ready() + assert.False(t, ok) + assert.Contains(t, msg, "No processes") +} + +func TestDelayedPhc2sysStartup(t *testing.T) { + profileName := "test-profile" + nodeProfile := ptpv1.PtpProfile{ + Name: &profileName, + } + + pm := &ProcessManager{ + process: []*ptpProcess{}, + } + + dn := &Daemon{ + processManager: pm, + } + + phc2sys := &ptpProcess{ + name: phc2sysProcessName, + skipInitialStartup: testSkipStartupReason, + nodeProfile: nodeProfile, + dn: dn, + execMutex: sync.Mutex{}, + stopped: true, // Simulated stopped state + } + + ts2phc := &ptpProcess{ + name: ts2phcProcessName, + nodeProfile: nodeProfile, + dn: dn, + eventCh: make(chan event.EventChannel, 10), + ptpClockThreshold: &ptpv1.PtpClockThreshold{ + MaxOffsetThreshold: 1000, + MinOffsetThreshold: -1000, + }, + } + + pm.process = append(pm.process, phc2sys, ts2phc) + + // 1. Simulate large offset (> 1s) + dn.delayedPhc2sys.Store(true) + largeOffset := 37000000000.0 // 37s + ts2phc.ProcessTs2PhcEvents(largeOffset, ts2phcProcessName, "eth0", event.PTP_FREERUN, nil) + + // Verify phc2sys is still delayed + assert.Equal(t, testSkipStartupReason, phc2sys.skipInitialStartup) + + // 2. Exact boundary (== 1s): should NOT clear the delay (condition is strictly <) + phc2sys.skipInitialStartup = testSkipStartupReason + dn.delayedPhc2sys.Store(true) + boundaryOffset := 1000000000.0 // exactly 1s + ts2phc.ProcessTs2PhcEvents(boundaryOffset, ts2phcProcessName, "eth0", event.PTP_FREERUN, nil) + assert.Equal(t, testSkipStartupReason, phc2sys.skipInitialStartup, "At the 1s boundary phc2sys should remain delayed") + assert.True(t, dn.delayedPhc2sys.Load()) + + // 3. Negative sub-second offset (-0.5s): math.Abs should clear the delay + phc2sys.skipInitialStartup = testSkipStartupReason + dn.delayedPhc2sys.Store(true) + negSmallOffset := -500000000.0 // -0.5s + ts2phc.ProcessTs2PhcEvents(negSmallOffset, ts2phcProcessName, "eth0", event.PTP_LOCKED, nil) + assert.Equal(t, "", phc2sys.skipInitialStartup, "Negative sub-second offset should clear the delay") + assert.False(t, dn.delayedPhc2sys.Load()) + + // 4. Negative super-second offset (-2s): should NOT clear the delay + phc2sys.skipInitialStartup = testSkipStartupReason + dn.delayedPhc2sys.Store(true) + negLargeOffset := -2000000000.0 // -2s + ts2phc.ProcessTs2PhcEvents(negLargeOffset, ts2phcProcessName, "eth0", event.PTP_FREERUN, nil) + assert.Equal(t, testSkipStartupReason, phc2sys.skipInitialStartup, "Negative super-second offset should keep the delay") + assert.True(t, dn.delayedPhc2sys.Load()) + + // 5. Simulate sub-second offset (< 1s): original passing case + phc2sys.skipInitialStartup = testSkipStartupReason + dn.delayedPhc2sys.Store(true) + smallOffset := 500000000.0 // 0.5s + ts2phc.ProcessTs2PhcEvents(smallOffset, ts2phcProcessName, "eth0", event.PTP_LOCKED, nil) + assert.Equal(t, "", phc2sys.skipInitialStartup) + assert.False(t, dn.delayedPhc2sys.Load()) +} + +// TestDelayedPhc2sysStartup_HAProfile verifies that a phc2sys process in a +// dedicated HA profile (with no ptp4l of its own) is correctly started when +// a sub-second offset is reported by a ptp4l in one of its haProfile entries. +func TestDelayedPhc2sysStartup_HAProfile(t *testing.T) { + phc2sysProfileName := "test-dual-nic-bc-ha" + master1ProfileName := "test-bc-master1" + master2ProfileName := "test-bc-master2" + + phc2sysNodeProfile := ptpv1.PtpProfile{Name: &phc2sysProfileName} + master1NodeProfile := ptpv1.PtpProfile{Name: &master1ProfileName} + master2NodeProfile := ptpv1.PtpProfile{Name: &master2ProfileName} + + pm := &ProcessManager{process: []*ptpProcess{}} + dn := &Daemon{processManager: pm} + + phc2sys := &ptpProcess{ + name: phc2sysProcessName, + skipInitialStartup: testSkipStartupReason, + nodeProfile: phc2sysNodeProfile, + haProfile: map[string][]string{master1ProfileName: {"ens1f1"}, master2ProfileName: {"ens2f0"}}, + dn: dn, + execMutex: sync.Mutex{}, + stopped: true, + } + + ptp4lMaster1 := &ptpProcess{ + name: ptp4lProcessName, + nodeProfile: master1NodeProfile, + dn: dn, + eventCh: make(chan event.EventChannel, 10), + ptpClockThreshold: &ptpv1.PtpClockThreshold{ + MaxOffsetThreshold: 1000, + MinOffsetThreshold: -1000, + }, + } + + ptp4lMaster2 := &ptpProcess{ + name: ptp4lProcessName, + nodeProfile: master2NodeProfile, + dn: dn, + eventCh: make(chan event.EventChannel, 10), + ptpClockThreshold: &ptpv1.PtpClockThreshold{ + MaxOffsetThreshold: 1000, + MinOffsetThreshold: -1000, + }, + } + + pm.process = append(pm.process, phc2sys, ptp4lMaster1, ptp4lMaster2) + + // A large offset from master1 must NOT start phc2sys. + dn.delayedPhc2sys.Store(true) + dn.HandleDelayedPhc2sysStartup(ptp4lProcessName, 37000000000.0, &master1ProfileName) + assert.Equal(t, testSkipStartupReason, phc2sys.skipInitialStartup, + "large offset from HA-linked profile should not start phc2sys") + + // A sub-second offset from master2 (different profile from phc2sys) MUST start it. + dn.delayedPhc2sys.Store(true) + dn.HandleDelayedPhc2sysStartup(ptp4lProcessName, 500000000.0, &master2ProfileName) + assert.Equal(t, "", phc2sys.skipInitialStartup, + "sub-second offset from HA-linked profile should start phc2sys") + assert.False(t, dn.delayedPhc2sys.Load()) +} + +func TestFindProcessesByName(t *testing.T) { + pm := &ProcessManager{ + process: []*ptpProcess{ + {name: "ptp4l"}, + {name: "phc2sys"}, + {name: "ptp4l"}, + }, + } + + procs := pm.findProcessesByName("ptp4l") + assert.Equal(t, 2, len(procs)) + assert.Equal(t, "ptp4l", procs[0].name) + assert.Equal(t, "ptp4l", procs[1].name) + + procs = pm.findProcessesByName("phc2sys") + assert.Equal(t, 1, len(procs)) + + procs = pm.findProcessesByName("nonexistent") + assert.Equal(t, 0, len(procs)) +} diff --git a/pkg/daemon/log_parsing.go b/pkg/daemon/log_parsing.go index eeb681d86..7f8848fe8 100644 --- a/pkg/daemon/log_parsing.go +++ b/pkg/daemon/log_parsing.go @@ -119,7 +119,13 @@ func processParsedMetrics(process *ptpProcess, ptpMetrics *parser.Metrics) { if ptpMetrics.Iface != "" && configName != "" { masterOffsetIface.set(configName, ptpMetrics.Iface) } + if ptpMetrics.Source == "master" && process.dn != nil { + process.dn.HandleDelayedPhc2sysStartup(process.name, ptpMetrics.Offset, process.nodeProfile.Name) + } case ts2phcProcessName: + if process.dn != nil { + process.dn.HandleDelayedPhc2sysStartup(process.name, ptpMetrics.Offset, process.nodeProfile.Name) + } // Send event for ts2phc eventSource := process.ifaces.GetEventSource(process.ifaces.GetPhcID2IFace(ptpMetrics.Iface)) values := map[event.ValueType]interface{}{ diff --git a/pkg/daemon/ready.go b/pkg/daemon/ready.go index 7038c7ba9..7454e7771 100644 --- a/pkg/daemon/ready.go +++ b/pkg/daemon/ready.go @@ -32,7 +32,12 @@ func (rt *ReadyTracker) Ready() (bool, string) { notRunning := strings.Builder{} noMetrics := strings.Builder{} + activeCount := 0 for _, p := range rt.processManager.process { + if p == nil || p.skipInitialStartup != "" { + continue + } + activeCount++ if p.Stopped() { if notRunning.Len() > 0 { notRunning.WriteString(", ") @@ -46,6 +51,10 @@ func (rt *ReadyTracker) Ready() (bool, string) { } } + if activeCount == 0 { + return false, "No processes have started" + } + if notRunning.Len() > 0 { return false, "Stopped process(es): " + notRunning.String() } diff --git a/pkg/daemon/testdata/profile-tbc-tr.yaml b/pkg/daemon/testdata/profile-tbc-tr.yaml index a2babe046..52bebda7c 100644 --- a/pkg/daemon/testdata/profile-tbc-tr.yaml +++ b/pkg/daemon/testdata/profile-tbc-tr.yaml @@ -6,30 +6,31 @@ ptpSettings: clockId[ens8f0]: 5799633565432596448 inSyncConditionThreshold: 10 inSyncConditionTimes: 2 + clockType: "T-BC" phc2sysOpts: -r -n 24 -N 8 -u 0 -s ens4f0 plugins: e810: enableDefaultConfig: false interconnections: - - id: ens4f0 - part: E810-XXVDA4T - gnssInput: false - upstreamPort: ens4f0 - phaseOutputConnectors: - - SMA1 - - SMA2 - - id: ens5f0 - part: E810-XXVDA4T - inputConnector: - connector: SMA1 - delayPs: 920 - - id: ens8f0 - part: E810-XXVDA4T - inputConnector: - connector: SMA1 - delayPs: 920 - phaseOutputConnectors: - - U.FL1 + - id: ens4f0 + part: E810-XXVDA4T + gnssInput: false + upstreamPort: ens4f0 + phaseOutputConnectors: + - SMA1 + - SMA2 + - id: ens5f0 + part: E810-XXVDA4T + inputConnector: + connector: SMA1 + delayPs: 920 + - id: ens8f0 + part: E810-XXVDA4T + inputConnector: + connector: SMA1 + delayPs: 920 + phaseOutputConnectors: + - U.FL1 pins: ens4f0: SMA1: 2 1 @@ -173,4 +174,4 @@ ts2phcConf: | ts2phc.extts_polarity rising ts2phc.extts_correction 0 ts2phc.master 0 -ts2phcOpts: '-s generic -a --ts2phc.rh_external_pps 1' +ts2phcOpts: "-s generic -a --ts2phc.rh_external_pps 1" diff --git a/pkg/daemon/testdata/profile-tbc-tt.yaml b/pkg/daemon/testdata/profile-tbc-tt.yaml index 613cf97c2..4803b991a 100644 --- a/pkg/daemon/testdata/profile-tbc-tt.yaml +++ b/pkg/daemon/testdata/profile-tbc-tt.yaml @@ -5,6 +5,7 @@ ptpSettings: clockId[ens5f0]: 5799633565433967128 clockId[ens8f0]: 5799633565432596448 controllingProfile: 01-tbc-tr + clockType: "T-BC" ptp4lConf: | [ens4f0] masterOnly 1 diff --git a/pkg/daemon/testdata/profile-tgm.yaml b/pkg/daemon/testdata/profile-tgm.yaml new file mode 100644 index 000000000..4fa5273aa --- /dev/null +++ b/pkg/daemon/testdata/profile-tgm.yaml @@ -0,0 +1,237 @@ +name: grandmaster +ptpSettings: + unitTest: /tmp/test + clockId[ens4f0]: 5799633565432596414 + clockId[ens5f0]: 5799633565433967128 + clockId[ens8f0]: 5799633565432596448 +phc2sysOpts: -a -r -n 24 -N 8 -R 16 -u 0 +plugins: + e810: + enableDefaultConfig: false + interconnections: + - id: ens4f0 + part: E810-XXVDA4T + gnssInput: true + phaseOutputConnectors: + - SMA1 + - SMA2 + - id: ens5f0 + part: E810-XXVDA4T + inputConnector: + connector: SMA1 + delayPs: 920 + - id: ens8f0 + part: E810-XXVDA4T + inputConnector: + connector: SMA1 + delayPs: 920 + phaseOutputConnectors: + - U.FL1 + pins: + ens4f0: + SMA1: 2 1 + SMA2: 2 2 + U.FL1: 0 1 + U.FL2: 0 2 + ens5f0: + SMA1: 1 1 + SMA2: 0 2 + U.FL1: 0 1 + U.FL2: 0 2 + ens8f0: + SMA1: 1 1 + SMA2: 0 2 + U.FL1: 0 1 + U.FL2: 0 2 + settings: + LocalHoldoverTimeout: 14400 + LocalMaxHoldoverOffSet: 1500 + MaxInSpecOffset: 1500 + ublxCmds: + - args: + - -P + - "29.20" + - -z + - CFG-HW-ANT_CFG_VOLTCTRL,1 + reportOutput: false + - args: + - -P + - "29.20" + - -e + - GPS + reportOutput: false + - args: + - -P + - "29.20" + - -d + - Galileo + reportOutput: false + - args: + - -P + - "29.20" + - -d + - GLONASS + reportOutput: false + - args: + - -P + - "29.20" + - -d + - BeiDou + reportOutput: false + - args: + - -P + - "29.20" + - -d + - SBAS + reportOutput: false + - args: + - -P + - "29.20" + - -t + - -w + - "5" + - -v + - "1" + - -e + - SURVEYIN,600,50000 + reportOutput: true + - args: + - -P + - "29.20" + - -p + - MON-HW + reportOutput: true + - args: + - -P + - "29.20" + - -p + - CFG-MSG,1,38,248 + reportOutput: true +ptp4lConf: | + [ens7f0] + masterOnly 1 + [ens7f1] + masterOnly 1 + [global] + # + # Default Data Set + # + twoStepFlag 1 + priority1 128 + priority2 128 + domainNumber 24 + #utc_offset 37 + clockClass 6 + clockAccuracy 0x27 + offsetScaledLogVariance 0xFFFF + free_running 0 + freq_est_interval 1 + dscp_event 0 + dscp_general 0 + dataset_comparison G.8275.x + G.8275.defaultDS.localPriority 128 + # + # Port Data Set + # + logAnnounceInterval -3 + logSyncInterval -4 + logMinDelayReqInterval -4 + logMinPdelayReqInterval 0 + announceReceiptTimeout 3 + syncReceiptTimeout 0 + delayAsymmetry 0 + fault_reset_interval 4 + neighborPropDelayThresh 20000000 + masterOnly 0 + G.8275.portDS.localPriority 128 + # + # Run time options + # + assume_two_step 0 + logging_level 6 + path_trace_enabled 0 + follow_up_info 0 + hybrid_e2e 0 + inhibit_multicast_service 0 + net_sync_monitor 0 + tc_spanning_tree 0 + tx_timestamp_timeout 50 + unicast_listen 0 + unicast_master_table 0 + unicast_req_duration 3600 + use_syslog 1 + verbose 0 + summary_interval -4 + kernel_leap 1 + check_fup_sync 0 + # + # Servo Options + # + pi_proportional_const 0.0 + pi_integral_const 0.0 + pi_proportional_scale 0.0 + pi_proportional_exponent -0.3 + pi_proportional_norm_max 0.7 + pi_integral_scale 0.0 + pi_integral_exponent 0.4 + pi_integral_norm_max 0.3 + step_threshold 0.0 + first_step_threshold 0.00002 + clock_servo pi + sanity_freq_limit 200000000 + ntpshm_segment 0 + # + # Transport options + # + transportSpecific 0x0 + ptp_dst_mac 01:1B:19:00:00:00 + p2p_dst_mac 01:80:C2:00:00:0E + udp_ttl 1 + udp6_scope 0x0E + uds_address /var/run/ptp4l + # + # Default interface options + # + clock_type BC + network_transport L2 + delay_mechanism E2E + time_stamping hardware + tsproc_mode filter + delay_filter moving_median + delay_filter_length 10 + egressLatency 0 + ingressLatency 0 + boundary_clock_jbod 0 + # + # Clock description + # + productDescription ;; + revisionData ;; + manufacturerIdentity 00:00:00 + userDescription ; + timeSource 0x20 +ptp4lOpts: -2 --summary_interval -4 +ptpSchedulingPolicy: SCHED_FIFO +ptpSchedulingPriority: 10 +ts2phcConf: | + [nmea] + ts2phc.master 1 + [global] + use_syslog 0 + verbose 1 + logging_level 7 + ts2phc.pulsewidth 100000000 + leapfile /usr/share/zoneinfo/leap-seconds.list + [ens7f0] + ts2phc.master 0 + ts2phc.extts_polarity rising + ts2phc.extts_correction 0 + [ens4f0] + ts2phc.master 0 + ts2phc.extts_polarity rising + ts2phc.extts_correction 0 + [ens2f0] + ts2phc.master 0 + ts2phc.extts_polarity rising + ts2phc.extts_correction 0 +ts2phcOpts: ' '