Skip to content

Commit

Permalink
Add DPLL Netlink Collector
Browse files Browse the repository at this point in the history
  • Loading branch information
nocturnalastro committed Nov 24, 2023
1 parent b5f6519 commit 86cc8f8
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 85 deletions.
47 changes: 43 additions & 4 deletions pkg/collectors/contexts/contexts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ package contexts
import (
"fmt"

corev1 "k8s.io/api/core/v1"

"github.com/redhat-partner-solutions/vse-sync-collection-tools/pkg/clients"
)

const (
PTPNamespace = "openshift-ptp"
PTPPodNamePrefix = "linuxptp-daemon-"
PTPContainer = "linuxptp-daemon-container"
GPSContainer = "gpsd"
PTPNamespace = "openshift-ptp"
PTPPodNamePrefix = "linuxptp-daemon-"
PTPContainer = "linuxptp-daemon-container"
GPSContainer = "gpsd"
NetlinkDebugPod = "ptp-dpll-netlink-debug-pod"
NetlinkDebugContainer = "ptp-dpll-netlink-debug-container"
NetlinkDebugContainerImage = "quay.io/jnunez/tools:dpll9.2_dev"
)

func GetPTPDaemonContext(clientset *clients.Clientset) (clients.ExecContext, error) {
Expand All @@ -22,3 +27,37 @@ func GetPTPDaemonContext(clientset *clients.Clientset) (clients.ExecContext, err
}
return ctx, nil
}

func GetNetlinkContext(clientset *clients.Clientset) (*clients.ContainerCreationExecContext, error) {
hpt := corev1.HostPathDirectory
ctx := clients.NewContainerCreationExecContext(
clientset,
PTPNamespace,
NetlinkDebugPod,
NetlinkDebugContainer,
NetlinkDebugContainerImage,
map[string]string{},
[]string{"sleep", "inf"},
&corev1.SecurityContext{
Capabilities: &corev1.Capabilities{
// Requires NET_ADMIN: having (NET_RAW + NET_BIND_SERVICE + NET_BROADCAST) does not work
// Without NET_ADMIN it will not connect to the netlink interface
// Requires SYS_AMDIN: having every other permission does not work.
// Without SYS_ADMIN lspci does not include the Serial number in the comments thefore can not calculate the clockID
Add: []corev1.Capability{
"SYS_ADMIN",
"NET_ADMIN",
},
},
},
true,
[]*clients.Volume{
{
Name: "modules",
MountPath: "/lib/modules",
VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/lib/modules", Type: &hpt}},
},
},
)
return ctx, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ const (
unitConversionFactor = 100
)

type DevDPLLInfo struct {
type DevFilesystemDPLLInfo struct {
Timestamp string `fetcherKey:"date" json:"timestamp"`
EECState string `fetcherKey:"dpll_0_state" json:"eecstate"`
PPSState string `fetcherKey:"dpll_1_state" json:"state"`
PPSOffset float64 `fetcherKey:"dpll_1_offset" json:"terror"`
}

// AnalyserJSON returns the json expected by the analysers
func (dpllInfo *DevDPLLInfo) GetAnalyserFormat() ([]*callbacks.AnalyserFormatType, error) {
func (dpllInfo *DevFilesystemDPLLInfo) GetAnalyserFormat() ([]*callbacks.AnalyserFormatType, error) {
formatted := callbacks.AnalyserFormatType{
ID: "dpll/time-error",
Data: map[string]any{
Expand All @@ -41,14 +41,14 @@ func (dpllInfo *DevDPLLInfo) GetAnalyserFormat() ([]*callbacks.AnalyserFormatTyp
}

var (
dpllFetcher map[string]*fetcher.Fetcher
dpllFSFetcher map[string]*fetcher.Fetcher
)

func init() {
dpllFetcher = make(map[string]*fetcher.Fetcher)
dpllFSFetcher = make(map[string]*fetcher.Fetcher)
}

func postProcessDPLL(result map[string]string) (map[string]any, error) {
func postProcessDPLLFilesystem(result map[string]string) (map[string]any, error) {
processedResult := make(map[string]any)
offset, err := strconv.ParseFloat(result["dpll_1_offset"], 32)
if err != nil {
Expand All @@ -58,9 +58,9 @@ func postProcessDPLL(result map[string]string) (map[string]any, error) {
return processedResult, nil
}

// BuildDPLLInfoFetcher popluates the fetcher required for
// BuildFilesystemDPLLInfoFetcher popluates the fetcher required for
// collecting the DPLLInfo
func BuildDPLLInfoFetcher(interfaceName string) error { //nolint:dupl // Further dedup risks be too abstract or fragile
func BuildFilesystemDPLLInfoFetcher(interfaceName string) error { //nolint:dupl // Further dedup risks be too abstract or fragile
fetcherInst, err := fetcher.FetcherFactory(
[]*clients.Cmd{dateCmd},
[]fetcher.AddCommandArgs{
Expand All @@ -85,21 +85,21 @@ func BuildDPLLInfoFetcher(interfaceName string) error { //nolint:dupl // Further
log.Errorf("failed to create fetcher for dpll: %s", err.Error())
return fmt.Errorf("failed to create fetcher for dpll: %w", err)
}
dpllFetcher[interfaceName] = fetcherInst
fetcherInst.SetPostProcessor(postProcessDPLL)
dpllFSFetcher[interfaceName] = fetcherInst
fetcherInst.SetPostProcessor(postProcessDPLLFilesystem)
return nil
}

// GetDevDPLLInfo returns the device DPLL info for an interface.
func GetDevDPLLInfo(ctx clients.ExecContext, interfaceName string) (DevDPLLInfo, error) {
dpllInfo := DevDPLLInfo{}
fetcherInst, fetchedInstanceOk := dpllFetcher[interfaceName]
// GetDevDPLLFilesystemInfo returns the device DPLL info for an interface.
func GetDevDPLLFilesystemInfo(ctx clients.ExecContext, interfaceName string) (DevFilesystemDPLLInfo, error) {
dpllInfo := DevFilesystemDPLLInfo{}
fetcherInst, fetchedInstanceOk := dpllFSFetcher[interfaceName]
if !fetchedInstanceOk {
err := BuildDPLLInfoFetcher(interfaceName)
err := BuildFilesystemDPLLInfoFetcher(interfaceName)
if err != nil {
return dpllInfo, err
}
fetcherInst, fetchedInstanceOk = dpllFetcher[interfaceName]
fetcherInst, fetchedInstanceOk = dpllFSFetcher[interfaceName]
if !fetchedInstanceOk {
return dpllInfo, errors.New("failed to create fetcher for DPLLInfo")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var _ = Describe("NewContainerContext", func() {

ctx, err := clients.NewContainerContext(clientset, "TestNamespace", "Test", "TestContainer")
Expect(err).NotTo(HaveOccurred())
info, err := devices.GetDevDPLLInfo(ctx, "aFakeInterface")
info, err := devices.GetDevDPLLFilesystemInfo(ctx, "aFakeInterface")
Expect(err).NotTo(HaveOccurred())
Expect(info.Timestamp).To(Equal("2023-06-16T11:49:47.0584Z"))
Expect(info.EECState).To(Equal(eecState))
Expand Down
209 changes: 209 additions & 0 deletions pkg/collectors/devices/dpll_netlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0-or-later

package devices

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"

log "github.com/sirupsen/logrus"

"github.com/redhat-partner-solutions/vse-sync-collection-tools/pkg/callbacks"
"github.com/redhat-partner-solutions/vse-sync-collection-tools/pkg/clients"
"github.com/redhat-partner-solutions/vse-sync-collection-tools/pkg/fetcher"
)

var states = map[string]string{
"unknown": "-1",
"invalid": "0",
"freerun": "1",
"locked": "2",
"locked-ho-acq": "3",
"holdover": "4",
}

type DevNetlinkDPLLInfo struct {
Timestamp string `fetcherKey:"date" json:"timestamp"`
EECState string `fetcherKey:"eec" json:"eecstate"`
PPSState string `fetcherKey:"pps" json:"state"`
}

// AnalyserJSON returns the json expected by the analysers
func (dpllInfo *DevNetlinkDPLLInfo) GetAnalyserFormat() ([]*callbacks.AnalyserFormatType, error) {
formatted := callbacks.AnalyserFormatType{
ID: "dpll/states",
Data: map[string]any{
"timestamp": dpllInfo.Timestamp,
"eecstate": dpllInfo.EECState,
"state": dpllInfo.PPSState,
},
}
return []*callbacks.AnalyserFormatType{&formatted}, nil
}

type NetlinkEntry struct {
LockStatus string `json:"lock-status"` //nolint:tagliatelle // not my choice
Driver string `json:"module-name"` //nolint:tagliatelle // not my choice
ClockType string `json:"type"` //nolint:tagliatelle // not my choice
ClockID int64 `json:"clock-id"` //nolint:tagliatelle // not my choice
ID int `json:"id"` //nolint:tagliatelle // not my choice
}

// # Example output
// [{'clock-id': 5799633565435100136,
// 'id': 0,
// 'lock-status': 'locked-ho-acq',
// 'mode': 'automatic',
// 'mode-supported': ['automatic'],
// 'module-name': 'ice',
// 'type': 'eec'},
// {'clock-id': 5799633565435100136,
// 'id': 1,
// 'lock-status': 'locked-ho-acq',
// 'mode': 'automatic',
// 'mode-supported': ['automatic'],
// 'module-name': 'ice',
// 'type': 'pps'}]

var (
dpllNetlinkFetcher map[int64]*fetcher.Fetcher
dpllClockIDFetcher map[string]*fetcher.Fetcher
)

func init() {
dpllNetlinkFetcher = make(map[int64]*fetcher.Fetcher)
dpllClockIDFetcher = make(map[string]*fetcher.Fetcher)
}

func buildPostProcessDPLLNetlink(clockID int64) fetcher.PostProcessFuncType {
return func(result map[string]string) (map[string]any, error) {
processedResult := make(map[string]any)

entries := make([]NetlinkEntry, 0)
cleaned := strings.ReplaceAll(result["dpll-netlink"], "'", "\"")
err := json.Unmarshal([]byte(cleaned), &entries)
if err != nil {
log.Errorf("Failed to unmarshal netlink output: %s", err.Error())
}

log.Debug("entries: ", entries)
for _, entry := range entries {
if entry.ClockID == clockID {
state, ok := states[entry.LockStatus]
if !ok {
log.Errorf("Unknown state: %s", state)
state = "-1"
}
processedResult[entry.ClockType] = state
}
}
return processedResult, nil
}
}

// BuildDPLLNetlinkInfoFetcher popluates the fetcher required for
// collecting the DPLLInfo
func BuildDPLLNetlinkInfoFetcher(clockID int64) error { //nolint:dupl // Further dedup risks be too abstract or fragile
fetcherInst, err := fetcher.FetcherFactory(
[]*clients.Cmd{dateCmd},
[]fetcher.AddCommandArgs{
{
Key: "dpll-netlink",
Command: "/linux/tools/net/ynl/cli.py --spec /linux/Documentation/netlink/specs/dpll.yaml --dump device-get",
Trim: true,
},
},
)
if err != nil {
log.Errorf("failed to create fetcher for dpll netlink: %s", err.Error())
return fmt.Errorf("failed to create fetcher for dpll netlink: %w", err)
}
dpllNetlinkFetcher[clockID] = fetcherInst
fetcherInst.SetPostProcessor(buildPostProcessDPLLNetlink(clockID))
return nil
}

// GetDevDPLLInfo returns the device DPLL info for an interface.
func GetDevDPLLNetlinkInfo(ctx clients.ExecContext, clockID int64) (DevNetlinkDPLLInfo, error) {
dpllInfo := DevNetlinkDPLLInfo{}
fetcherInst, fetchedInstanceOk := dpllNetlinkFetcher[clockID]
if !fetchedInstanceOk {
err := BuildDPLLNetlinkInfoFetcher(clockID)
if err != nil {
return dpllInfo, err
}
fetcherInst, fetchedInstanceOk = dpllNetlinkFetcher[clockID]
if !fetchedInstanceOk {
return dpllInfo, errors.New("failed to create fetcher for DPLLInfo using netlink interface")
}
}
err := fetcherInst.Fetch(ctx, &dpllInfo)
if err != nil {
log.Debugf("failed to fetch dpllInfo via netlink: %s", err.Error())
return dpllInfo, fmt.Errorf("failed to fetch dpllInfo via netlink: %w", err)
}
return dpllInfo, nil
}

func BuildClockIDFetcher(interfaceName string) error {
fetcherInst, err := fetcher.FetcherFactory(
[]*clients.Cmd{dateCmd},
[]fetcher.AddCommandArgs{
{
Key: "dpll-netlink-clock-id",
Command: fmt.Sprintf(
`export IFNAME=%s; export BUSID=$(readlink /sys/class/net/$IFNAME/device | xargs basename | cut -d ':' -f 2,3);`+
` echo $(("16#$(lspci -v | grep $BUSID -A 20 |grep 'Serial Number' | awk '{print $NF}' | tr -d '-')"))`,
interfaceName,
),
Trim: true,
},
},
)
if err != nil {
log.Errorf("failed to create fetcher for dpll clock ID: %s", err.Error())
return fmt.Errorf("failed to create fetcher for dpll clock ID: %w", err)
}
fetcherInst.SetPostProcessor(postProcessDPLLNetlinkClockID)
dpllClockIDFetcher[interfaceName] = fetcherInst
return nil
}

func postProcessDPLLNetlinkClockID(result map[string]string) (map[string]any, error) {
processedResult := make(map[string]any)
clockID, err := strconv.ParseInt(result["dpll-netlink-clock-id"], 10, 64)
if err != nil {
return processedResult, fmt.Errorf("failed to parse int for clock id: %w", err)
}
processedResult["clockID"] = clockID
return processedResult, nil
}

type NetlinkClockID struct {
Timestamp string `fetcherKey:"date" json:"timestamp"`
ClockID int64 `fetcherKey:"clockID" json:"clockId"`
}

func GetClockID(ctx clients.ExecContext, interfaceName string) (NetlinkClockID, error) {
clockID := NetlinkClockID{}
fetcherInst, fetchedInstanceOk := dpllClockIDFetcher[interfaceName]
if !fetchedInstanceOk {
err := BuildClockIDFetcher(interfaceName)
if err != nil {
return clockID, err
}
fetcherInst, fetchedInstanceOk = dpllClockIDFetcher[interfaceName]
if !fetchedInstanceOk {
return clockID, errors.New("failed to create fetcher for DPLLInfo using netlink interface")
}
}
err := fetcherInst.Fetch(ctx, &clockID)
if err != nil {
log.Debugf("failed to fetch netlink clockID %s", err.Error())
return clockID, fmt.Errorf("failed to fetch netlink clockID %w", err)
}
return clockID, nil
}
Loading

0 comments on commit 86cc8f8

Please sign in to comment.