Skip to content

Commit

Permalink
add leap event handling
Browse files Browse the repository at this point in the history
this commit adds leap event handling that includes following:
1. Determine that current time is within -12h...1m from the leap
2. Send PMC command (once) to indicate leap61 or leap 59 to the
downstream architecture and
3. Handle ts2phc time ambiguity

Signed-off-by: Vitaly Grinberg <[email protected]>
  • Loading branch information
vitus133 committed Sep 2, 2024
1 parent 3280bc0 commit 17fee9e
Show file tree
Hide file tree
Showing 17 changed files with 1,329 additions and 35 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21
require github.com/openshift/ptp-operator v0.0.0-20230207052655-ede9197d99ca

require (
github.com/bigkevmcd/go-configparser v0.0.0-20240808124832-fc81059ea0bd
github.com/facebook/time v0.0.0-20230529151911-512b3b30ab23
github.com/golang/glog v1.0.0
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
Expand All @@ -19,6 +20,7 @@ require (
k8s.io/apimachinery v0.25.4
k8s.io/client-go v0.25.4
k8s.io/utils v0.0.0-20221012122500-cfd413dd9e85
sigs.k8s.io/yaml v1.3.0
)

require (
Expand Down Expand Up @@ -88,5 +90,4 @@ require (
sigs.k8s.io/controller-runtime v0.13.0 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bigkevmcd/go-configparser v0.0.0-20240808124832-fc81059ea0bd h1:MsTk4yo6KVYdulsDscuH4AwiZN1CyuCJAg59EWE7HPQ=
github.com/bigkevmcd/go-configparser v0.0.0-20240808124832-fc81059ea0bd/go.mod h1:vzEQfW+A1T+AMJmTIX+SXNLNECHOM7GEinHhw0IjykI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
Expand Down Expand Up @@ -217,6 +219,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down Expand Up @@ -247,7 +251,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
Expand Down Expand Up @@ -631,8 +634,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
Expand Down
10 changes: 7 additions & 3 deletions pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/openshift/linuxptp-daemon/pkg/config"
"github.com/openshift/linuxptp-daemon/pkg/dpll"
"github.com/openshift/linuxptp-daemon/pkg/leap"

"github.com/openshift/linuxptp-daemon/pkg/event"
ptpnetwork "github.com/openshift/linuxptp-daemon/pkg/network"
Expand Down Expand Up @@ -456,6 +457,7 @@ func (dn *Daemon) applyNodePtpProfile(runID int, nodeProfile *ptpv1.PtpProfile)
configFile = fmt.Sprintf("ts2phc.%d.config", runID)
configPath = fmt.Sprintf("/var/run/%s", configFile)
messageTag = fmt.Sprintf("[ts2phc.%d.config:{level}]", runID)
leap.LeapMgr.SetPtp4lConfigPath(fmt.Sprintf("ptp4l.%d.config", runID))
}

if configOpts == nil || *configOpts == "" {
Expand Down Expand Up @@ -887,9 +889,11 @@ func (p *ptpProcess) cmdRun(stdoutToSocket bool) {

// for ts2phc along with processing metrics need to identify event
func (p *ptpProcess) processPTPMetrics(output string) {
if p.name == ts2phcProcessName && (strings.Contains(output, NMEASourceDisabledIndicator) ||
strings.Contains(output, InvalidMasterTimestampIndicator) ||
strings.Contains(output, NMEASourceDisabledIndicator2)) { //TODO identify which interface lost nmea or 1pps
if p.name == ts2phcProcessName &&
!leap.LeapMgr.IsLeapInWindow(time.Now().UTC(), -2*time.Second, time.Second) &&
(strings.Contains(output, NMEASourceDisabledIndicator) ||
strings.Contains(output, InvalidMasterTimestampIndicator) ||
strings.Contains(output, NMEASourceDisabledIndicator2)) { //TODO identify which interface lost nmea or 1pps
iface := p.ifaces.GetGMInterface().Name
p.ProcessTs2PhcEvents(faultyOffset, ts2phcProcessName, iface, map[event.ValueType]interface{}{event.NMEA_STATUS: int64(0)})
glog.Error("nmea string lost") //TODO: add for 1pps lost
Expand Down
9 changes: 6 additions & 3 deletions pkg/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package daemon_test
import (
"flag"
"fmt"
ptpv1 "github.com/openshift/ptp-operator/api/v1"
"k8s.io/utils/pointer"
"os"
"strings"
"testing"

ptpv1 "github.com/openshift/ptp-operator/api/v1"
"k8s.io/utils/pointer"

"github.com/openshift/linuxptp-daemon/pkg/config"
"github.com/openshift/linuxptp-daemon/pkg/daemon"
"github.com/openshift/linuxptp-daemon/pkg/leap"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -231,7 +233,8 @@ func TestMain(m *testing.M) {
os.Exit(code)
}
func Test_ProcessPTPMetrics(t *testing.T) {

assert.NoError(t, leap.MockLeapFile())
defer close(leap.LeapMgr.Close)
assert := assert.New(t)
for _, tc := range testCases {
tc.node = MYNODE
Expand Down
3 changes: 2 additions & 1 deletion pkg/event/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ func TestEventHandler_ProcessEvents(t *testing.T) {
eventManager := event.Init("node", true, "/tmp/go.sock", eChannel, closeChn, nil, nil, nil)
eventManager.MockEnable()
go eventManager.ProcessEvents()
assert.NoError(t, mockLeap())
assert.NoError(t, leap.MockLeapFile())
defer close(leap.LeapMgr.Close)
time.Sleep(1 * time.Second)
for _, test := range tests {
select {
Expand Down
128 changes: 104 additions & 24 deletions pkg/leap/leap-file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ import (
"time"

"github.com/golang/glog"
"github.com/openshift/linuxptp-daemon/pkg/pmc"
"github.com/openshift/linuxptp-daemon/pkg/ublox"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
fake "k8s.io/client-go/kubernetes/fake"
)

const (
Expand All @@ -29,6 +32,8 @@ const (
leapSourceGps = 2
leapConfigMapName = "leap-configmap"
MaintenancePeriod = time.Minute * 1
pmcWindowStartHours = 12
pmcWindowEndSeconds = 60
)

type LeapManager struct {
Expand All @@ -49,8 +54,10 @@ type LeapManager struct {
leapFilePath string
leapFileName string
// UTC offset and its validity time
utcOffset int
utcOffsetTime time.Time
utcOffset int
utcOffsetTime time.Time
ptp4lConfigPath string
pmcLeapSent bool
}

type LeapEvent struct {
Expand Down Expand Up @@ -93,27 +100,6 @@ func New(kubeclient kubernetes.Interface, namespace string) (*LeapManager, error
return LeapMgr, nil
}

func (l *LeapManager) Run() {
glog.Info("starting Leap file manager")
ticker := time.NewTicker(MaintenancePeriod)
defer ticker.Stop()
for {
select {
case v := <-l.UbloxLsInd:
l.handleLeapIndication(&v)
case <-l.Close:
LeapMgr = nil
return
case <-ticker.C:
if l.retryUpdate {
l.updateLeapConfigmap()
}
// TODO: if current time is within -12h ... +60s from leap event:
// Send PMC command
}
}
}

func GetUtcOffset() int {
if LeapMgr != nil {
if time.Now().UTC().After(LeapMgr.utcOffsetTime) {
Expand Down Expand Up @@ -172,6 +158,11 @@ func parseLeapFile(b []byte) (*LeapFile, error) {
return &l, nil
}

func (l *LeapManager) SetPtp4lConfigPath(path string) {
glog.Info("set Leap manager ptp4l config file name to ", path)
l.ptp4lConfigPath = path
}

func (l *LeapManager) renderLeapData() (*bytes.Buffer, error) {
templateStr := `# Do not edit
# This file is generated automatically by linuxptp-daemon
Expand Down Expand Up @@ -246,6 +237,55 @@ func (l *LeapManager) populateLeapData() error {
return nil
}

func (l *LeapManager) Run() {
glog.Info("starting Leap file manager")
ticker := time.NewTicker(MaintenancePeriod)
for {
select {
case v := <-l.UbloxLsInd:
l.handleLeapIndication(&v)
case <-l.Close:
LeapMgr = nil
return
case <-ticker.C:
if l.retryUpdate {
l.updateLeapConfigmap()
}
if l.IsLeapInWindow(time.Now().UTC(), -pmcWindowStartHours*time.Hour, -pmcWindowEndSeconds*time.Second) {
if !l.pmcLeapSent {
g, err := pmc.RunPMCExpGetGMSettings(l.ptp4lConfigPath)
if err != nil {
glog.Error("error in Leap:", err)
continue
}
leapDiff := l.leapFile.LeapEvents[len(l.leapFile.LeapEvents)-1].LeapSec - int(g.TimePropertiesDS.CurrentUtcOffset)
if leapDiff > 0 {
g.TimePropertiesDS.Leap59 = false
g.TimePropertiesDS.Leap61 = true
} else if leapDiff < 0 {
g.TimePropertiesDS.Leap59 = true
g.TimePropertiesDS.Leap61 = false
} else {
// No actual change in leap seconds, don't send anything
l.pmcLeapSent = true
continue
}
glog.Info("Sending PMC command in Leap window")
glog.Infof("Leap time properties: %++v", g.TimePropertiesDS)
err = pmc.RunPMCExpSetGMSettings(l.ptp4lConfigPath, g)
if err != nil {
glog.Error("failed to send PMC for Leap: ", err)
continue
}
l.pmcLeapSent = true
}
} else {
l.pmcLeapSent = false
}
}
}
}

// updateLeapFile updates a new leap event to the list of leap events, if provided
func (l *LeapManager) updateLeapFile(leapTime time.Time,
leapSec int, currentTime time.Time) {
Expand Down Expand Up @@ -366,7 +406,8 @@ func (l *LeapManager) processLeapIndication(data *ublox.TimeLs) (*leapIndResult,
return nil, fmt.Errorf("failed to parse time duration: Leap: %v", err)
}
if data.LsChange == 0 && data.TimeToLsEvent >= 0 {
result.leapTime = currentTime
// shift leap date out of pmc window, so no pmc commands will be sent
result.leapTime = currentTime.Add(-pmcWindowEndSeconds * time.Second)
} else {
result.leapTime = gpsStartTime.Add(deltaHours)
}
Expand All @@ -377,3 +418,42 @@ func (l *LeapManager) processLeapIndication(data *ublox.TimeLs) (*leapIndResult,
}
return nil, nil
}

// IsLeapInWindow() returns whether a leap event is occuring within the specified time window from now
func (l *LeapManager) IsLeapInWindow(now time.Time, startOffset, endOffset time.Duration) bool {
startTime := time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)
lastLeap := l.leapFile.LeapEvents[len(l.leapFile.LeapEvents)-1]
lastLeapTime, err := strconv.Atoi(lastLeap.LeapTime)
if err != nil {
return false
}
leapTime := startTime.Add(time.Second * time.Duration(lastLeapTime))
leapWindowStart := leapTime.Add(startOffset)
leapWindowEnd := leapTime.Add(endOffset)
if now.After(leapWindowStart) && now.Before(leapWindowEnd) {
glog.Info("Leap in window: ", startOffset, " ", endOffset)
return true
}
return false
}

func MockLeapFile() error {
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: "openshift-ptp", Name: "leap-configmap"},
Data: map[string]string{
"test-node-name": `# Do not edit
# This file is generated automatically by linuxptp-daemon
#$ 3927775672
#@ 4291747200
3692217600 37 # 1 Jan 2017`,
},
}
os.Setenv("NODE_NAME", "test-node-name")
client := fake.NewSimpleClientset(cm)
lm, err := New(client, "openshift-ptp")
if err != nil {
return err
}
go lm.Run()
return nil
}
46 changes: 45 additions & 1 deletion pkg/leap/leap-file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func Test_processLeapIndication_MissedLeapZero(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, res)
assert.WithinDuration(t, time.Now().UTC(), res.updateTime, 1*time.Second)
assert.WithinDuration(t, res.leapTime, time.Now().UTC(), 1*time.Second)
assert.WithinDuration(t, res.leapTime, time.Now().UTC().Add(-60*time.Second), 1*time.Second)
assert.Equal(t, int(ind.CurrLs+ind.LsChange+19), res.leapSec)
}

Expand Down Expand Up @@ -300,6 +300,50 @@ func Test_handleLeapIndication(t *testing.T) {
assert.Equal(t, "4291747200", lm.leapFile.ExpirationTime)
}

func Test_IsLeapInWindow_Pos(t *testing.T) {
now := time.Now().UTC()
startTime := time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)
diff := now.Sub(startTime)
leapTime := fmt.Sprintf("%d", int(diff.Seconds()))
lm := &LeapManager{

leapFile: LeapFile{
LeapEvents: []LeapEvent{
{
LeapTime: leapTime,
},
},
},
}
res := lm.IsLeapInWindow(now, -1*time.Second, time.Second)
assert.True(t, res)
}
func Test_IsLeapInWindow_Neg(t *testing.T) {
now := time.Now().UTC()
startTime := time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)
diff := now.Sub(startTime)
leapTime := fmt.Sprintf("%d", int(diff.Seconds())-1)
lm := &LeapManager{

leapFile: LeapFile{
LeapEvents: []LeapEvent{
{
LeapTime: leapTime,
},
},
},
}
res := lm.IsLeapInWindow(now, -1*time.Second, time.Second)
assert.False(t, res)
}

func Test_SetPtp4lConfigPath(t *testing.T) {
path := "test"
lm := &LeapManager{}
lm.SetPtp4lConfigPath(path)
assert.Equal(t, path, lm.ptp4lConfigPath)
}

func Test_New_Good(t *testing.T) {
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: "openshift-ptp", Name: "leap-configmap"},
Expand Down
19 changes: 19 additions & 0 deletions vendor/github.com/bigkevmcd/go-configparser/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 17fee9e

Please sign in to comment.