Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ FROM registry.ci.openshift.org/ocp/4.18:base-rhel9
RUN yum -y update && yum -y update glibc && yum --setopt=skip_missing_names_on_install=False -y install linuxptp ethtool hwdata synce4l && yum clean all


RUN yum install -y procps-ng
RUN yum install -y chrony
RUN yum install -y gpsd-minimal
RUN yum install -y gpsd-minimal-clients

Expand Down
231 changes: 231 additions & 0 deletions addons/generic/ntpfailover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package generic

import (
"encoding/json"
"regexp"
"sync"
"time"

"github.com/golang/glog"

"github.com/openshift/linuxptp-daemon/pkg/plugin"
ptpv1 "github.com/openshift/ptp-operator/api/v1"
)

type ntpFailoverPluginData struct {
gnssFailover bool
cmdSetEnabled map[string]func(bool)
pcfsmState int
pcfsmMutex sync.Mutex
pcfsmLocked bool
ts2phcTolerance time.Duration
startupDelay time.Duration
expiryTime time.Time
}

type ntpFailoverOpts struct {
StartupDelay string `json:"startupDelay"`
Ts2phcTolerance string `json:"ts2phcTolerance"` //nolint:stylecheck
GnssFailover bool `json:"gnssFailover"`
}

const ( // phc2sys/chronyd Finite State Machine States
pcsmsStartupDefault int = iota // Just started, both are unknown
pcsmsStartupPhc2sys // phc2sys setting time
pcsmsStartupChronyd // phc2sys setting time
pcsmsStartupBoth // phc2sys setting time
pcsmsActive // phc2sys setting time
pcsmsOutOfSpec // switch to chronyd setting time
pcsmsFailover // chronyd setting time
)

const (
ts2phcPname = "ts2phc"
chronydPname = "chronyd"
phc2sysPname = "phc2sys"
)

var (
ts2phcOffsetRegex = regexp.MustCompile("offset .*s[23] freq")
chronydOnlineRegex = regexp.MustCompile("chronyd .* starting")
)

func onPTPConfigChangeNtpFailover(data *interface{}, nodeProfile *ptpv1.PtpProfile) error {
var _ntpFailoverOpts ntpFailoverOpts
_ntpFailoverOpts.StartupDelay = "90s"
_ntpFailoverOpts.Ts2phcTolerance = "5s"
_ntpFailoverOpts.GnssFailover = false
var err error
if data != nil {
_data := *data
var pluginData = _data.(*ntpFailoverPluginData)
if pluginData.cmdSetEnabled == nil {
pluginData.cmdSetEnabled = make(map[string]func(bool))
}
for name, opts := range (*nodeProfile).Plugins {
if name == "ntpfailover" {
optsByteArray, _ := json.Marshal(opts)
err = json.Unmarshal(optsByteArray, &_ntpFailoverOpts)
if err != nil {
glog.Error("ntpfailover failed to unmarshal opts: " + err.Error())
}
}
}

pluginData.gnssFailover = _ntpFailoverOpts.GnssFailover

pluginData.startupDelay, err = time.ParseDuration(_ntpFailoverOpts.StartupDelay)
if err != nil {
glog.Infof("Failed parsing startupDelay %s: %d. Defaulting to 90 seconds.", _ntpFailoverOpts.StartupDelay, err)
pluginData.startupDelay, _ = time.ParseDuration("90s")
}
pluginData.expiryTime = time.Now().Add(pluginData.startupDelay)

pluginData.ts2phcTolerance, err = time.ParseDuration(_ntpFailoverOpts.Ts2phcTolerance)
if err != nil {
glog.Infof("Failed parsing ts2phcTolerance %s: %d. Defaulting to 5 seconds.", _ntpFailoverOpts.Ts2phcTolerance, err)
pluginData.ts2phcTolerance, _ = time.ParseDuration("5s")
}
}
return nil
}

func registerProcessNtpFailover(data *interface{}, pname string, cmdSetEnabled func(bool)) {
if data != nil {
_data := *data

var pluginData = _data.(*ntpFailoverPluginData)
if pluginData.gnssFailover {
if pluginData.cmdSetEnabled == nil {
pluginData.cmdSetEnabled = make(map[string]func(bool))
}
pluginData.cmdSetEnabled[pname] = cmdSetEnabled
}
}
}

func processLogNtpFailover(data *interface{}, pname string, log string) string {
ret := log
if data != nil {
_data := *data

var pluginData = _data.(*ntpFailoverPluginData)
if pluginData.gnssFailover {
currentTime := time.Now()

if pname == ts2phcPname && ts2phcOffsetRegex.MatchString(log) {
pluginData.expiryTime = currentTime.Add(pluginData.ts2phcTolerance)
}

pluginData.pcfsmMutex.Lock()
ownLock := !pluginData.pcfsmLocked //If locked, then skip, otherwise take lock
pluginData.pcfsmMutex.Unlock()
if ownLock {
done:
for {
switch pluginData.pcfsmState {
case pcsmsStartupDefault:
_, foundChronyd := pluginData.cmdSetEnabled[chronydPname]
_, foundPhc2Sys := pluginData.cmdSetEnabled[phc2sysPname]
if foundChronyd && foundPhc2Sys {
pluginData.pcfsmState = pcsmsStartupBoth
} else if foundChronyd {
pluginData.pcfsmState = pcsmsStartupChronyd
} else if foundPhc2Sys {
pluginData.pcfsmState = pcsmsStartupPhc2sys
} else {
break done
}
case pcsmsStartupPhc2sys:
_, foundChronyd := pluginData.cmdSetEnabled[chronydPname]
if foundChronyd {
pluginData.pcfsmState = pcsmsStartupBoth
} else {
break done
}
case pcsmsStartupChronyd:
_, foundPhc2Sys := pluginData.cmdSetEnabled[phc2sysPname]
if foundPhc2Sys {
pluginData.pcfsmState = pcsmsStartupBoth
} else {
break done
}
case pcsmsStartupBoth:
chronydSetEnabled, ok := pluginData.cmdSetEnabled[chronydPname]
if ok {
chronydSetEnabled(false)
}
phc2sysSetEnabled, ok := pluginData.cmdSetEnabled[phc2sysPname]
if ok {
phc2sysSetEnabled(true)
}
pluginData.pcfsmState = pcsmsActive
continue
case pcsmsActive:
if pname == ts2phcPname {
if currentTime.After(pluginData.expiryTime) {
pluginData.pcfsmState = pcsmsOutOfSpec
continue
}
}
if pname == chronydPname && chronydOnlineRegex.MatchString(log) {
chronydSetEnabled, ok := pluginData.cmdSetEnabled[chronydPname]
if ok {
chronydSetEnabled(false)
}
}
break done
case pcsmsOutOfSpec:
if pname == ts2phcPname {
if currentTime.After(pluginData.expiryTime) {
pluginData.pcfsmState = pcsmsFailover
chronydSetEnabled, ok := pluginData.cmdSetEnabled[chronydPname]
if ok {
chronydSetEnabled(true)
}
phc2sysSetEnabled, ok := pluginData.cmdSetEnabled[phc2sysPname]
if ok {
phc2sysSetEnabled(false)
}
continue
}
}
break done
case pcsmsFailover:
if pname == ts2phcPname {
if currentTime.Before(pluginData.expiryTime) {
pluginData.pcfsmState = pcsmsStartupDefault
continue
}
}
break done
}
}
pluginData.pcfsmMutex.Lock()
pluginData.pcfsmLocked = false //If took lock, then return it
pluginData.pcfsmMutex.Unlock()
}
}
}

return ret
}

// NtpFailover initializes NtpFailover plugin
func NtpFailover(name string) (*plugin.Plugin, *interface{}) {
if name != "ntpfailover" {
glog.Errorf("Plugin must be initialized as 'ntpfailover'")
return nil, nil
}
glog.Infof("registering ntpfailover plugin")
_plugin := plugin.Plugin{Name: "ntpfailover",
OnPTPConfigChange: onPTPConfigChangeNtpFailover,
RegisterEnableCallback: registerProcessNtpFailover,
ProcessLog: processLogNtpFailover,
}
pluginData := ntpFailoverPluginData{pcfsmState: pcsmsStartupDefault,
pcfsmMutex: sync.Mutex{}}
pluginData.cmdSetEnabled = make(map[string]func(bool))
var iface interface{} = &pluginData
return &_plugin, &iface
}
5 changes: 3 additions & 2 deletions addons/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

var PluginMapping = map[string]plugin.New{
"reference": generic.Reference,
"e810": intel.E810,
"reference": generic.Reference,
"ntpfailover": generic.NtpFailover,
"e810": intel.E810,
}
39 changes: 21 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,44 @@ go 1.22.0

require (
github.com/bigkevmcd/go-configparser v0.0.0-20240624060122-ccd05f93a9d2
github.com/facebook/time v0.0.0-20230529151911-512b3b30ab23
github.com/facebook/time v0.0.0-20241030181404-3e1b98825c29
github.com/golang/glog v1.2.4
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
github.com/jaypipes/ghw v0.12.0
github.com/mdlayher/genetlink v1.3.2
github.com/mdlayher/netlink v1.7.2
github.com/openshift/ptp-operator v0.0.0-20240623153039-2f8235d2dad8
github.com/openshift/ptp-operator v0.0.0-20251126211433-0dee6a03855c
github.com/prometheus/client_golang v1.16.0
github.com/sirupsen/logrus v1.9.0
github.com/sirupsen/logrus v1.9.3
github.com/stratoberry/go-gpsd v1.1.0
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
golang.org/x/sync v0.10.0
k8s.io/api v0.28.3
k8s.io/apimachinery v0.28.3
k8s.io/client-go v0.28.3
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
sigs.k8s.io/yaml v1.3.0
k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3
sigs.k8s.io/yaml v1.4.0
)

require (
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
Expand All @@ -63,29 +64,31 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/apiextensions-apiserver v0.28.3 // indirect
k8s.io/component-base v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
sigs.k8s.io/controller-runtime v0.16.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

replace github.com/openshift/ptp-operator => github.com/josephdrichard/ptp-operator v0.0.0-20251205063307-b04f1e9f5fc5
Loading