diff --git a/README.md b/README.md index c123e4527..3091da842 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ - [PTP Operator](#ptp-operator) - [PtpOperatorConfig](#ptpoperatorconfig) - [PtpConfig](#ptpconfig) +- [PtpConfig Status](#ptpconfig-status) - [Quick Start](#quick-start) ## PTP Operator @@ -314,6 +315,146 @@ Two ptp4l configurations must exist with the phc2sysOPts field set to an empty s The names of these ptp4l configurations will be used and listed under the ptpSettings/haProfiles key in the phc2sys-only enabled ptpConfig. +## PtpConfig Status +PtpConfig includes a status subresource with `ptpStatus.conditions[]` that operands (linuxptp-daemon, cloud-event-proxy) can update. Each condition includes `type`, `profile` (logical profile), `filename` (active config file), `status`, `reason`, `message`, and `lastUpdateTime`. + +Example populated status (GM): +```yaml +status: + matchList: + - nodeName: worker-0 + profile: GM-Profile-A + ptpStatus: + conditions: + - type: PTP4lRunning + profile: GM-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Started + message: "ptp4l is running (pid 3124)" + lastUpdateTime: "2025-06-27T10:13:10Z" + - type: LockState + profile: GM-Profile-A + filename: ptp4l.0.config + status: "True" + reason: LOCKED + message: "clock state is LOCKED" + lastUpdateTime: "2025-06-27T10:15:00Z" + - type: ClockClass + profile: GM-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Class6 + message: "clock class is 6" + lastUpdateTime: "2025-06-27T10:15:00Z" + - type: PortState + profile: GM-Profile-A + filename: ptp4l.0.config + status: "True" + reason: MASTER + message: "interface ens5f0 state MASTER" + lastUpdateTime: "2025-06-27T10:13:50Z" + - type: ClockType + profile: GM-Profile-A + filename: ptp4l.0.config + status: "True" + reason: T-GM + message: "profile reports T-GM Clock" + lastUpdateTime: "2025-06-27T10:13:00Z" + +Example populated status (OC): +```yaml +status: + ptpStatus: + conditions: + - type: PTP4lRunning + profile: OC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Started + message: "ptp4l is running" + lastUpdateTime: "2025-06-27T11:05:00Z" + - type: LockState + profile: OC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: LOCKED + message: "OC locked to GM" + lastUpdateTime: "2025-06-27T11:06:10Z" + - type: ClockClass + profile: OC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Class6 + message: "clock class is 6" + lastUpdateTime: "2025-06-27T11:06:10Z" + - type: PortState + profile: OC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: SLAVE + message: "interface ens5f1 state SLAVE (receiving time)" + lastUpdateTime: "2025-06-27T11:05:20Z" + - type: ClockType + profile: OC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: OC + message: "profile reports Ordinary Clock" + lastUpdateTime: "2025-06-27T11:05:00Z" +``` + +Example populated status (BC): +```yaml +status: + ptpStatus: + conditions: + - type: PTP4lRunning + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Started + message: "ptp4l is running" + lastUpdateTime: "2025-06-27T12:00:00Z" + - type: LockState + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: HOLDOVER + message: "BC in holdover" + lastUpdateTime: "2025-06-27T12:01:00Z" + - type: ClockClass + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: Class7 + message: "clock class is 7" + lastUpdateTime: "2025-06-27T12:01:00Z" + - type: PortState + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: SLAVE + message: "ingress port ens5f1 state SLAVE (receiving time)" + lastUpdateTime: "2025-06-27T12:00:20Z" + - type: PortState + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: MASTER + message: "egress port ens5f0 state MASTER (transmitting time)" + lastUpdateTime: "2025-06-27T12:00:25Z" + - type: ClockType + profile: BC-Profile-A + filename: ptp4l.0.config + status: "True" + reason: BC + message: "profile reports Boundary Clock" + lastUpdateTime: "2025-06-27T12:00:00Z" +``` + +RBAC for linuxptp-daemon to update `ptpconfigs/status` is included in `bindata/linuxptp/ptp-daemon.yaml`. + ## Quick Start To install PTP Operator: diff --git a/api/v1/ptpconfig_types.go b/api/v1/ptpconfig_types.go index fcdf58b3b..112ae971a 100644 --- a/api/v1/ptpconfig_types.go +++ b/api/v1/ptpconfig_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + corev1 "k8s.io/api/core/v1" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -38,6 +39,7 @@ type PtpConfigStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file MatchList []NodeMatchList `json:"matchList,omitempty"` + PtpStatus PtpStatus `json:"ptpStatus,omitempty"` } //+kubebuilder:object:root=true @@ -112,6 +114,60 @@ type NodeMatchList struct { Profile *string `json:"profile"` } +// PtpConditionType represents categories of PTP conditions reported by operands +type PtpConditionType string + +const ( + // Process lifecycle + ConditionPodRestarted PtpConditionType = "PodRestarted" + ConditionPTP4lRunning PtpConditionType = "PTP4lRunning" + ConditionPhc2sysRunning PtpConditionType = "Phc2sysRunning" + ConditionTs2phcRunning PtpConditionType = "Ts2phcRunning" + ConditionSynce4lRunning PtpConditionType = "Synce4lRunning" + ConditionChronydRunning PtpConditionType = "ChronydRunning" + ConditionGPSPipeRunning PtpConditionType = "GPSPipeRunning" + ConditionGPSDRunning PtpConditionType = "GPSDRunning" + + // Profile/apply + ConditionProfileApplied PtpConditionType = "ProfileApplied" + + // NIC/PHC capabilities + ConditionNICCapabilities PtpConditionType = "NICCapabilities" + + // State and class (values reflected in Reason/Message) + ConditionLockState PtpConditionType = "LockState" // LOCKED/HOLDOVER/FREERUN + ConditionClockClass PtpConditionType = "ClockClass" // 6/7/248 + ConditionPortState PtpConditionType = "PortState" // INITIALIZING/LISTENING/SLAVE/MASTER/FAULTY + ConditionClockType PtpConditionType = "ClockType" // OC/BC + + // Event/CEP + ConditionEventFramework PtpConditionType = "EventFramework" + + // GNSS/DPLL (when applicable) + ConditionGNSSState PtpConditionType = "GNSSState" + ConditionDPLLStatus PtpConditionType = "DPLLStatus" + + // Overall service readiness + ConditionPTPServiceReady PtpConditionType = "PTPServiceReady" +) + +// PtpCondition mirrors standard Kubernetes condition patterns +type PtpCondition struct { + Type PtpConditionType `json:"type"` + Profile string `json:"profile,omitempty"` + Filename string `json:"filename,omitempty"` + Process string `json:"process,omitempty"` + Status corev1.ConditionStatus `json:"status"` + Reason string `json:"reason,omitempty"` + Message string `json:"message,omitempty"` + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` +} + +// PtpStatus aggregates conditions emitted by operands for this PtpConfig +type PtpStatus struct { + Conditions []PtpCondition `json:"conditions,omitempty"` +} + func init() { SchemeBuilder.Register(&PtpConfig{}, &PtpConfigList{}) } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 2f39482d1..c13a56083 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -211,6 +211,22 @@ func (in *PtpClockThreshold) DeepCopy() *PtpClockThreshold { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PtpCondition) DeepCopyInto(out *PtpCondition) { + *out = *in + in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PtpCondition. +func (in *PtpCondition) DeepCopy() *PtpCondition { + if in == nil { + return nil + } + out := new(PtpCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PtpConfig) DeepCopyInto(out *PtpConfig) { *out = *in @@ -309,6 +325,7 @@ func (in *PtpConfigStatus) DeepCopyInto(out *PtpConfigStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + in.PtpStatus.DeepCopyInto(&out.PtpStatus) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PtpConfigStatus. @@ -616,3 +633,25 @@ func (in *PtpRecommend) DeepCopy() *PtpRecommend { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PtpStatus) DeepCopyInto(out *PtpStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]PtpCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PtpStatus. +func (in *PtpStatus) DeepCopy() *PtpStatus { + if in == nil { + return nil + } + out := new(PtpStatus) + in.DeepCopyInto(out) + return out +} diff --git a/bindata/linuxptp/ptp-daemon.yaml b/bindata/linuxptp/ptp-daemon.yaml index 904f0770b..f2b05d9fd 100644 --- a/bindata/linuxptp/ptp-daemon.yaml +++ b/bindata/linuxptp/ptp-daemon.yaml @@ -227,6 +227,35 @@ subjects: name: prometheus-k8s namespace: openshift-monitoring --- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ptpconfig-status-writer + namespace: openshift-ptp +rules: + - apiGroups: + - ptp.openshift.io + resources: + - ptpconfigs/status + verbs: + - get + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: ptpconfig-status-writer-linuxptp + namespace: openshift-ptp +subjects: + - kind: ServiceAccount + name: linuxptp-daemon + namespace: openshift-ptp +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: ptpconfig-status-writer +--- apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: diff --git a/bundle/manifests/ptp-operator.clusterserviceversion.yaml b/bundle/manifests/ptp-operator.clusterserviceversion.yaml index b08046c95..9e8d8a559 100644 --- a/bundle/manifests/ptp-operator.clusterserviceversion.yaml +++ b/bundle/manifests/ptp-operator.clusterserviceversion.yaml @@ -61,7 +61,7 @@ metadata: categories: Networking certified: "false" containerImage: quay.io/openshift/origin-ptp-operator:4.21 - createdAt: "2025-09-25T09:20:14Z" + createdAt: "2025-10-03T17:05:44Z" description: This software enables configuration of Precision Time Protocol(PTP) on Kubernetes. It detects hardware capable PTP devices on each node, and configures linuxptp processes such as ptp4l, phc2sys and timemaster. diff --git a/bundle/manifests/ptp.openshift.io_ptpconfigs.yaml b/bundle/manifests/ptp.openshift.io_ptpconfigs.yaml index f673bee62..1e36e394a 100644 --- a/bundle/manifests/ptp.openshift.io_ptpconfigs.yaml +++ b/bundle/manifests/ptp.openshift.io_ptpconfigs.yaml @@ -150,6 +150,40 @@ spec: - profile type: object type: array + ptpStatus: + description: PtpStatus aggregates conditions emitted by operands for + this PtpConfig + properties: + conditions: + items: + description: PtpCondition mirrors standard Kubernetes condition + patterns + properties: + filename: + type: string + lastUpdateTime: + format: date-time + type: string + message: + type: string + process: + type: string + profile: + type: string + reason: + type: string + status: + type: string + type: + description: PtpConditionType represents categories of PTP + conditions reported by operands + type: string + required: + - status + - type + type: object + type: array + type: object type: object type: object served: true diff --git a/config/crd/bases/ptp.openshift.io_ptpconfigs.yaml b/config/crd/bases/ptp.openshift.io_ptpconfigs.yaml index ff8edd7be..424b39dd0 100644 --- a/config/crd/bases/ptp.openshift.io_ptpconfigs.yaml +++ b/config/crd/bases/ptp.openshift.io_ptpconfigs.yaml @@ -150,6 +150,40 @@ spec: - profile type: object type: array + ptpStatus: + description: PtpStatus aggregates conditions emitted by operands for + this PtpConfig + properties: + conditions: + items: + description: PtpCondition mirrors standard Kubernetes condition + patterns + properties: + filename: + type: string + lastUpdateTime: + format: date-time + type: string + message: + type: string + process: + type: string + profile: + type: string + reason: + type: string + status: + type: string + type: + description: PtpConditionType represents categories of PTP + conditions reported by operands + type: string + required: + - status + - type + type: object + type: array + type: object type: object type: object served: true diff --git a/manifests/stable/ptp-operator.clusterserviceversion.yaml b/manifests/stable/ptp-operator.clusterserviceversion.yaml index b08046c95..9e8d8a559 100644 --- a/manifests/stable/ptp-operator.clusterserviceversion.yaml +++ b/manifests/stable/ptp-operator.clusterserviceversion.yaml @@ -61,7 +61,7 @@ metadata: categories: Networking certified: "false" containerImage: quay.io/openshift/origin-ptp-operator:4.21 - createdAt: "2025-09-25T09:20:14Z" + createdAt: "2025-10-03T17:05:44Z" description: This software enables configuration of Precision Time Protocol(PTP) on Kubernetes. It detects hardware capable PTP devices on each node, and configures linuxptp processes such as ptp4l, phc2sys and timemaster. diff --git a/manifests/stable/ptp.openshift.io_ptpconfigs.yaml b/manifests/stable/ptp.openshift.io_ptpconfigs.yaml index f673bee62..1e36e394a 100644 --- a/manifests/stable/ptp.openshift.io_ptpconfigs.yaml +++ b/manifests/stable/ptp.openshift.io_ptpconfigs.yaml @@ -150,6 +150,40 @@ spec: - profile type: object type: array + ptpStatus: + description: PtpStatus aggregates conditions emitted by operands for + this PtpConfig + properties: + conditions: + items: + description: PtpCondition mirrors standard Kubernetes condition + patterns + properties: + filename: + type: string + lastUpdateTime: + format: date-time + type: string + message: + type: string + process: + type: string + profile: + type: string + reason: + type: string + status: + type: string + type: + description: PtpConditionType represents categories of PTP + conditions reported by operands + type: string + required: + - status + - type + type: object + type: array + type: object type: object type: object served: true diff --git a/pkg/status/ptpconfig_status.go b/pkg/status/ptpconfig_status.go new file mode 100644 index 000000000..2fac5f1a6 --- /dev/null +++ b/pkg/status/ptpconfig_status.go @@ -0,0 +1,50 @@ +package status + +import ( + "context" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + ptpv1 "github.com/k8snetworkplumbingwg/ptp-operator/api/v1" + clientset "github.com/k8snetworkplumbingwg/ptp-operator/pkg/client/clientset/versioned" +) + +// UpsertPtpCondition updates or appends a condition on the PtpConfig status and issues UpdateStatus with retry +func UpsertPtpCondition(ctx context.Context, cs clientset.Interface, namespace, name string, cond ptpv1.PtpCondition) error { + backoff := wait.Backoff{Duration: 100 * time.Millisecond, Factor: 1.5, Jitter: 0.1, Steps: 6} + return wait.ExponentialBackoff(backoff, func() (bool, error) { + pc, err := cs.PtpV1().PtpConfigs(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, err + } + + // upsert by composite key: Type + Profile + Filename + Process + updated := false + for i := range pc.Status.PtpStatus.Conditions { + c := &pc.Status.PtpStatus.Conditions[i] + if c.Type == cond.Type && c.Profile == cond.Profile && c.Filename == cond.Filename && c.Process == cond.Process { + *c = cond + updated = true + break + } + } + if !updated { + pc.Status.PtpStatus.Conditions = append(pc.Status.PtpStatus.Conditions, cond) + } + + // enforce max history + const maxConditions = 100 + if n := len(pc.Status.PtpStatus.Conditions); n > maxConditions { + pc.Status.PtpStatus.Conditions = pc.Status.PtpStatus.Conditions[n-maxConditions:] + } + + _, err = cs.PtpV1().PtpConfigs(namespace).UpdateStatus(ctx, pc, metav1.UpdateOptions{}) + if err != nil { + // retry on conflict or transient errors + return false, nil + } + return true, nil + }) +}