From 1d41200a67a09139bfcc127f6c69a685c4d3b2ae Mon Sep 17 00:00:00 2001 From: Jeremy Poulin Date: Tue, 21 Oct 2025 14:15:54 -0400 Subject: [PATCH] OCPEDGE-2084: Add PacemakerStatus CRD for two-node fencing Introduces etcd.openshift.io/v1alpha1 API group with a PacemakerCluster custom resource. This provides visibility into Pacemaker cluster health for Two Node Fencing (TNF) etcd deployments. The status-only resource is populated by a privileged controller and consumed by the cluster-etcd-operator healthcheck controller. This API is not explicitly gated because it's only created by CEO once the transition to an ExternalEtcd has occured. This means that it is naturally gated by the TNF topology. --- etcd/.codegen.yaml | 2 + etcd/README.md | 46 ++ etcd/install.go | 26 + etcd/v1alpha1/Makefile | 7 + etcd/v1alpha1/README.md | 43 ++ etcd/v1alpha1/doc.go | 8 + etcd/v1alpha1/register.go | 39 ++ .../AAA_ungated.yaml | 18 + etcd/v1alpha1/types_pacemakercluster.go | 516 ++++++++++++++++++ ...0000_25_etcd_01_pacemakerclusters.crd.yaml | 425 +++++++++++++++ .../zz_generated.crd-manifests/doc.go | 1 + etcd/v1alpha1/zz_generated.deepcopy.go | 248 +++++++++ ..._generated.featuregated-crd-manifests.yaml | 21 + .../AAA_ungated.yaml | 426 +++++++++++++++ .../zz_generated.swagger_doc_generated.go | 124 +++++ hack/update-payload-crds.sh | 1 + install.go | 2 + .../generated_openapi/zz_generated.openapi.go | 482 ++++++++++++++++ openapi/openapi.json | 270 +++++++++ ...0000_25_etcd_01_pacemakerclusters.crd.yaml | 425 +++++++++++++++ 20 files changed, 3130 insertions(+) create mode 100644 etcd/.codegen.yaml create mode 100644 etcd/README.md create mode 100644 etcd/install.go create mode 100644 etcd/v1alpha1/Makefile create mode 100644 etcd/v1alpha1/README.md create mode 100644 etcd/v1alpha1/doc.go create mode 100644 etcd/v1alpha1/register.go create mode 100644 etcd/v1alpha1/tests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml create mode 100644 etcd/v1alpha1/types_pacemakercluster.go create mode 100644 etcd/v1alpha1/zz_generated.crd-manifests/0000_25_etcd_01_pacemakerclusters.crd.yaml create mode 100644 etcd/v1alpha1/zz_generated.crd-manifests/doc.go create mode 100644 etcd/v1alpha1/zz_generated.deepcopy.go create mode 100644 etcd/v1alpha1/zz_generated.featuregated-crd-manifests.yaml create mode 100644 etcd/v1alpha1/zz_generated.featuregated-crd-manifests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml create mode 100644 etcd/v1alpha1/zz_generated.swagger_doc_generated.go create mode 100644 payload-manifests/crds/0000_25_etcd_01_pacemakerclusters.crd.yaml diff --git a/etcd/.codegen.yaml b/etcd/.codegen.yaml new file mode 100644 index 00000000000..ffa2c8d9b2e --- /dev/null +++ b/etcd/.codegen.yaml @@ -0,0 +1,2 @@ +swaggerdocs: + commentPolicy: Warn diff --git a/etcd/README.md b/etcd/README.md new file mode 100644 index 00000000000..ee1fefa99d1 --- /dev/null +++ b/etcd/README.md @@ -0,0 +1,46 @@ +# etcd.openshift.io API Group + +This API group contains CRDs related to etcd cluster management. Specifically, this is only used for TNF (Two Node Fencing) +for gathering status updates from the node to ensure the cluster-admin is warned about unhealthy setups. + +## API Versions + +### v1alpha1 + +Contains the `PacemakerCluster` custom resource for monitoring Pacemaker cluster health in TNF (Two Node Fencing) deployments. + +#### PacemakerCluster + +- **Feature Gate**: None - this CRD is gated by cluster-etcd-operator start-up. It will only be created once a TNF cluster has transitioned to external etcd. +- **Component**: `two-node-fencing` +- **Scope**: Cluster-scoped singleton resource named "cluster" +- **Resource Path**: `pacemakerclusters.etcd.openshift.io` + +The `PacemakerCluster` resource provides visibility into the health and status of a Pacemaker-managed cluster. It is periodically updated by the cluster-etcd-operator's status collector running as a privileged CronJob. + +**Status Fields:** +- `lastUpdated` (required): Timestamp when status was last collected - used to detect stale data +- `summary`: High-level cluster health metrics + - `pacemakerDaemonState`: Running state (enum: `Running`, `KnownNotRunning`) + - `quorumStatus`: Quorum state (enum: `Quorate`, `NoQuorum`) + - `nodesOnline`, `nodesTotal`: Node counts (0-2) + - `resourcesStarted`, `resourcesTotal`: Resource counts (0-16) +- `nodes`: Detailed status of each node (1-2 nodes) + - Name, IPv4/IPv6 addresses, online status (enum), mode (enum: `Active`, `Standby`) +- `resources`: Detailed status of each resource (1-16 resources) + - Name, resource agent, role (enum: `Started`, `Stopped`), active status (enum), node assignment +- `nodeHistory`: Recent operation failures for troubleshooting (up to 16 entries, last 5 minutes) +- `fencingHistory`: Recent fencing events (up to 16 events, last 24 hours) + - Target node, action (enum: `reboot`, `off`, `on`), status (enum: `success`, `failed`, `pending`), completion timestamp +- `collectionError`: Any errors encountered during status collection (max 2KB) +- `rawXML`: Full XML output from `pcs status xml` for debugging (max 256KB) + +**Design Principles:** +The API follows "Act on Deterministic Information": +- All fields except `lastUpdated` are optional +- Missing data indicates unknown state, not error +- Operator only acts on definitive information +- Unknown state preserves the last known health condition + +**Usage:** +The cluster-etcd-operator healthcheck controller watches this resource and updates operator conditions based on the cluster state. diff --git a/etcd/install.go b/etcd/install.go new file mode 100644 index 00000000000..7e7474152c4 --- /dev/null +++ b/etcd/install.go @@ -0,0 +1,26 @@ +package etcd + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + v1alpha1 "github.com/openshift/api/etcd/v1alpha1" +) + +const ( + GroupName = "etcd.openshift.io" +) + +var ( + schemeBuilder = runtime.NewSchemeBuilder(v1alpha1.Install) + // Install is a function which adds every version of this group to a scheme + Install = schemeBuilder.AddToScheme +) + +func Resource(resource string) schema.GroupResource { + return schema.GroupResource{Group: GroupName, Resource: resource} +} + +func Kind(kind string) schema.GroupKind { + return schema.GroupKind{Group: GroupName, Kind: kind} +} diff --git a/etcd/v1alpha1/Makefile b/etcd/v1alpha1/Makefile new file mode 100644 index 00000000000..cbb3b3638b7 --- /dev/null +++ b/etcd/v1alpha1/Makefile @@ -0,0 +1,7 @@ +.PHONY: verify-with-container +verify-with-container: + $(MAKE) -f ../../Makefile $@ + +.PHONY: update-with-container +update-with-container: + $(MAKE) -f ../../Makefile $@ diff --git a/etcd/v1alpha1/README.md b/etcd/v1alpha1/README.md new file mode 100644 index 00000000000..ac01049965c --- /dev/null +++ b/etcd/v1alpha1/README.md @@ -0,0 +1,43 @@ +# etcd.openshift.io/v1alpha1 + +This API group contains types related to two-node fencing for etcd cluster management. + +## PacemakerCluster + +The `PacemakerCluster` CRD provides visibility into the health and status of Pacemaker-managed clusters in dual-replica (two-node) OpenShift deployments. + +### Feature Gate + +- **Feature Gate**: None - this CRD is gated by cluster-etcd-operator start-up. It will only be created once a TNF cluster has transitioned to external etcd. +- **Component**: `two-node-fencing` + +### Usage + +The PacemakerCluster resource is a cluster-scoped, status-only singleton named "cluster". It is periodically updated by a privileged controller that runs `pcs status xml` and parses the output into structured fields for health checking. + +### Status Fields + +- **LastUpdated** (required): Timestamp when status was last collected +- **Summary**: High-level cluster state including: + - `pacemakerDaemonState`: Running state of the pacemaker daemon (enum: `Running`, `KnownNotRunning`) + - `quorumStatus`: Whether cluster has quorum (enum: `Quorate`, `NoQuorum`) + - `nodesOnline`, `nodesTotal`: Node counts + - `resourcesStarted`, `resourcesTotal`: Resource counts +- **Nodes**: Detailed per-node status (name, IPv4/IPv6 addresses, online status, mode) +- **Resources**: Detailed per-resource status (name, resource agent type, role enum, active status, node assignment) +- **NodeHistory**: Recent operation history for troubleshooting (operation failures within last 5 minutes) +- **FencingHistory**: Recent fencing events (events within last 24 hours) +- **RawXML**: Complete XML output from `pcs status xml` (for debugging only, max 256KB) +- **CollectionError**: Any errors encountered during status collection + +### Design Principles + +The API follows a "Design Principle: Act on Deterministic Information" approach: +- Almost all fields are optional except `lastUpdated` +- Missing data means "unknown" not "error" +- The operator only transitions between PacemakerHealthy and PacemakerDegraded states based on deterministic information +- When information is unavailable, the last known state is preserved + +### Notes + +The spec field is reserved but unused - all meaningful data is in the status subresource. diff --git a/etcd/v1alpha1/doc.go b/etcd/v1alpha1/doc.go new file mode 100644 index 00000000000..22151ff58b1 --- /dev/null +++ b/etcd/v1alpha1/doc.go @@ -0,0 +1,8 @@ +// +k8s:deepcopy-gen=package,register +// +k8s:defaulter-gen=TypeMeta +// +k8s:openapi-gen=true +// +openshift:featuregated-schema-gen=true + +// +kubebuilder:validation:Optional +// +groupName=etcd.openshift.io +package v1alpha1 diff --git a/etcd/v1alpha1/register.go b/etcd/v1alpha1/register.go new file mode 100644 index 00000000000..1dc6482f832 --- /dev/null +++ b/etcd/v1alpha1/register.go @@ -0,0 +1,39 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +var ( + GroupName = "etcd.openshift.io" + GroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + schemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // Install is a function which adds this version to a scheme + Install = schemeBuilder.AddToScheme + + // SchemeGroupVersion generated code relies on this name + // Deprecated + SchemeGroupVersion = GroupVersion + // AddToScheme exists solely to keep the old generators creating valid code + // DEPRECATED + AddToScheme = schemeBuilder.AddToScheme +) + +// Resource generated code relies on this being here, but it logically belongs to the group +// DEPRECATED +func Resource(resource string) schema.GroupResource { + return schema.GroupResource{Group: GroupName, Resource: resource} +} + +func addKnownTypes(scheme *runtime.Scheme) error { + metav1.AddToGroupVersion(scheme, GroupVersion) + + scheme.AddKnownTypes(GroupVersion, + &PacemakerCluster{}, + &PacemakerClusterList{}, + ) + + return nil +} diff --git a/etcd/v1alpha1/tests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml b/etcd/v1alpha1/tests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml new file mode 100644 index 00000000000..07ed11749be --- /dev/null +++ b/etcd/v1alpha1/tests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml @@ -0,0 +1,18 @@ +apiVersion: apiextensions.k8s.io/v1 # Hack because controller-gen complains if we don't have this +name: "PacemakerCluster" +crdName: pacemakerclusters.etcd.openshift.io +tests: + onCreate: + - name: Should be able to create a minimal PacemakerCluster + initial: | + apiVersion: etcd.openshift.io/v1alpha1 + kind: PacemakerCluster + metadata: + name: cluster + spec: {} + expected: | + apiVersion: etcd.openshift.io/v1alpha1 + kind: PacemakerCluster + metadata: + name: cluster + spec: {} diff --git a/etcd/v1alpha1/types_pacemakercluster.go b/etcd/v1alpha1/types_pacemakercluster.go new file mode 100644 index 00000000000..0c7c88bc032 --- /dev/null +++ b/etcd/v1alpha1/types_pacemakercluster.go @@ -0,0 +1,516 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// PacemakerDaemonStateType represents the state of the pacemaker daemon +// PacemakerDaemonStateType can be one of the following values: +// - Running - the pacemaker daemon is in the 'running' state +// - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state +// to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. +// +kubebuilder:validation:Enum=Running;KnownNotRunning +type PacemakerDaemonStateType string + +const ( + // PacemakerDaemonStateRunning indicates the pacemaker daemon is in the 'running' state. + PacemakerDaemonStateRunning PacemakerDaemonStateType = "Running" + + // PacemakerDaemonStateNotRunning indicates the pacemaker daemon is not in the 'running' state. + // This is left as a blanket state to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. + PacemakerDaemonStateNotRunning PacemakerDaemonStateType = "KnownNotRunning" +) + +// QuorumStatusType represents the quorum status of a Pacemaker cluster +// QuorumStatusType can be one of the following values: +// - Quorate - the cluster has quorum +// - NoQuorum - the cluster does not have quorum +// +kubebuilder:validation:Enum=Quorate;NoQuorum +type QuorumStatusType string + +const ( + // QuorumStatusQuorate indicates the cluster has quorum + QuorumStatusQuorate QuorumStatusType = "Quorate" + + // QuorumStatusNoQuorum indicates the cluster does not have quorum + QuorumStatusNoQuorum QuorumStatusType = "NoQuorum" +) + +// NodeOnlineStatusType represents whether a node is online or offline +// NodeOnlineStatusType can be one of the following values: +// - Online - the node is online +// - Offline - the node is offline +// +kubebuilder:validation:Enum=Online;Offline +type NodeOnlineStatusType string + +const ( + // NodeOnlineStatusOnline indicates the node is online + NodeOnlineStatusOnline NodeOnlineStatusType = "Online" + + // NodeOnlineStatusOffline indicates the node is offline + NodeOnlineStatusOffline NodeOnlineStatusType = "Offline" +) + +// NodeModeType represents whether a node is in active or standby mode +// NodeModeType can be one of the following values: +// - Active - the node is in active mode +// - Standby - the node is in standby mode +// +kubebuilder:validation:Enum=Active;Standby +type NodeModeType string + +const ( + // NodeModeActive indicates the node is in active mode + NodeModeActive NodeModeType = "Active" + + // NodeModeStandby indicates the node is in standby mode + NodeModeStandby NodeModeType = "Standby" +) + +// ResourceRoleType represents the role of a resource in the Pacemaker cluster +// ResourceRoleType can be one of the following values: +// - Started - the resource is started +// - Stopped - the resource is stopped +// We don't use promoted and unpromoted, so resources in those roles would omit the role field. +// +kubebuilder:validation:Enum=Started;Stopped +type ResourceRoleType string + +const ( + // ResourceRoleStarted indicates the resource is started + ResourceRoleStarted ResourceRoleType = "Started" + + // ResourceRoleStopped indicates the resource is stopped + ResourceRoleStopped ResourceRoleType = "Stopped" +) + +// ResourceActiveStatusType represents whether a resource is active or inactive +// ResourceActiveStatusType can be one of the following values: +// - Active - the resource is active +// - Inactive - the resource is inactive +// +kubebuilder:validation:Enum=Active;Inactive +type ResourceActiveStatusType string + +const ( + // ResourceActiveStatusActive indicates the resource is active + ResourceActiveStatusActive ResourceActiveStatusType = "Active" + + // ResourceActiveStatusInactive indicates the resource is inactive + ResourceActiveStatusInactive ResourceActiveStatusType = "Inactive" +) + +// FencingActionType represents the action taken during a fencing event +// FencingActionType can be one of the following values: +// - Reboot - the node was rebooted +// - Off - the node was turned off +// - On - the node was turned on +// +kubebuilder:validation:Enum=reboot;off;on +type FencingActionType string + +const ( + // FencingActionReboot indicates the node was rebooted + FencingActionReboot FencingActionType = "reboot" + + // FencingActionOff indicates the node was turned off + FencingActionOff FencingActionType = "off" + + // FencingActionOn indicates the node was turned on + FencingActionOn FencingActionType = "on" +) + +// FencingStatusType represents the status of a fencing event +// FencingStatusType can be one of the following values: +// - Success - the fencing event was successful +// - Failed - the fencing event failed +// - Pending - the fencing event is pending +// +kubebuilder:validation:Enum=success;failed;pending +type FencingStatusType string + +const ( + // FencingStatusSuccess indicates the fencing event was successful + FencingStatusSuccess FencingStatusType = "success" + + // FencingStatusFailed indicates the fencing event failed + FencingStatusFailed FencingStatusType = "failed" + + // FencingStatusPending indicates the fencing event is pending + FencingStatusPending FencingStatusType = "pending" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +// # PacemakerCluster represents the current state of the Pacemaker cluster as reported by the pcs status command +// +// This resource provides a view into the health and status of a Pacemaker-managed cluster in dual-replica (two-node) +// deployments. The status is periodically collected by a privileged controller and made available for monitoring +// and health checking purposes. +// +// Design Principle: Act on Deterministic Information +// Almost all fields are optional and will be populated if the data is available. If a field is not populated, it means that +// no actions can be taken by the cluster-etcd-operator with regard to whether pacemaker is healthy. This means that the +// operator will only transition between the PacemakerHealthy and PacemakerDegraded states based on deterministic information. Otherwise, +// the last known PacemakerHealthy or PacemakerDegraded status will be preserved. +// +// Some examples actions taken on deterministic information would be: +// - If the cluster is known to have quorum and critical resources are started, the operator report PacemakerHealthy state. +// - If the cluster is known to have lost quorum, or one or more critical resources are not started, the operator report PacemakerDegraded state. +// - If the cluster is trying to replace a failed node, it will check to see if the replacement node matches the expected node configuration in +// pacemaker before proceeding with node replacement operations. + +// Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support. +// +openshift:compatibility-gen:level=4 +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=pacemakerclusters,scope=Cluster +// +kubebuilder:subresource:status +// +openshift:file-pattern=cvoRunLevel=0000_25,operatorName=etcd,operatorOrdering=01,operatorComponent=two-node-fencing +// +openshift:api-approved.openshift.io=https://github.com/openshift/api/pull/2544 +type PacemakerCluster struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is an empty spec to satisfy Kubernetes API conventions. + // PacemakerCluster is a status-only resource and does not use spec for configuration. + // +optional + Spec *PacemakerClusterSpec `json:"spec,omitempty"` + + // status contains the actual pacemaker cluster status information collected from the cluster. + // This follows the Design Principle: Act on Deterministic Information. + // When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator. + // +optional + Status *PacemakerClusterStatus `json:"status,omitempty,omitzero"` +} + +// PacemakerClusterSpec is an empty spec as PacemakerCluster is a status-only resource +type PacemakerClusterSpec struct { +} + +// PacemakerClusterStatus contains the actual pacemaker cluster status information +// As part of validating the status object, we need to ensure that the lastUpdated timestamp is always newer than the current value. +// We allow the lastUpdated timestamp to be empty on initial creation, but it is required once it has been set. +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.lastUpdated) || (has(self.lastUpdated) && self.lastUpdated >= oldSelf.lastUpdated)",message="lastUpdated must be a newer timestamp than the current value" +type PacemakerClusterStatus struct { + // lastUpdated is the timestamp when this status was last updated + // When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value + // This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. + // It is optional upon initial creation, but required once it has been set. + // +kubebuilder:validation:Format=RFC3339 + // +optional + LastUpdated metav1.Time `json:"lastUpdated,omitempty"` + + // rawXML contains the raw XML output from pcs status xml command. + // Kept for debugging purposes only; healthcheck should not need to parse this. + // When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. + // When not present, the raw XML output is not available. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=262144 + // +optional + RawXML string `json:"rawXML,omitempty"` + + // collectionError contains any error encountered while collecting status + // When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. + // When not present, no collection errors are available and the status collection is assumed to be successful. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=2048 + // +optional + CollectionError string `json:"collectionError,omitempty"` + + // summary provides high-level counts and flags for the cluster state + // TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. + // When present, it must be a valid PacemakerSummary object. + // When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output. + // +optional + Summary *PacemakerSummary `json:"summary,omitempty"` + + // nodes provides detailed information about each node in the cluster + // When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. + // When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. + // If only one node is present, this indicates that the cluster is in the process of replacing a failed node. + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=2 + // +optional + Nodes []PacemakerNodeStatus `json:"nodes,omitempty"` + + // resources provides detailed information about each resource in the cluster + // When present, it must be a list of 1 or more PacemakerResourceStatus objects. + // When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. + // The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. + // The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + // This could drift over time as Two Node Fencing matures, so this is left flexible. + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=16 + // +optional + Resources []PacemakerResourceStatus `json:"resources,omitempty"` + + // nodeHistory provides recent operation history for troubleshooting + // When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. + // When not present, the node history is not available. This is the expected status for a healthy cluster. + // Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster + // is presented as a recorded event. + // The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported). + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=16 + // +optional + NodeHistory []PacemakerNodeHistoryEntry `json:"nodeHistory,omitempty"` + + // fencingHistory provides recent fencing events + // When present, it must be a list of 1 or more PacemakerFencingEvent objects. + // When not present, the fencing history is not available. This is the expected status for a healthy cluster. + // Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster + // is presented as a recorded event. + // The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported). + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=16 + // +optional + FencingHistory []PacemakerFencingEvent `json:"fencingHistory,omitempty"` +} + +// PacemakerSummary provides a high-level summary of cluster state +type PacemakerSummary struct { + // pacemakerDaemonState indicates the state of the pacemaker daemon + // PacemakerDaemonStateType can be one of the following values: + // - Running - the pacemaker daemon is in the 'running' state + // - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state + // to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. + // When present, it must be a valid PacemakerDaemonStateType. + // When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + PacemakerDaemonState PacemakerDaemonStateType `json:"pacemakerDaemonState,omitempty"` + + // quorumStatus indicates if the cluster has quorum + // QuorumStatusType can be one of the following values: + // - Quorate - the cluster has quorum + // - NoQuorum - the cluster does not have quorum + // When present, it must be a valid QuorumStatusType. + // When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + QuorumStatus QuorumStatusType `json:"quorumStatus,omitempty"` + + // nodesOnline is the count of online nodes + // When present, it must be a valid integer between 0 and 2 inclusive. + // When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=2 + // +optional + NodesOnline *int32 `json:"nodesOnline,omitempty"` + + // nodesTotal is the total count of configured nodes + // When present, it must be a valid integer between 0 and 2 inclusive. + // When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=2 + // +optional + NodesTotal *int32 `json:"nodesTotal,omitempty"` + + // resourcesStarted is the count of started resources + // When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + // The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + // The number could be less than 6 if the cluster is starting up or not healthy. + // The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + // When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=16 + // +optional + ResourcesStarted *int32 `json:"resourcesStarted,omitempty"` + + // resourcesTotal is the total count of configured resources + // When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + // The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + // The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + // When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=16 + // +optional + ResourcesTotal *int32 `json:"resourcesTotal,omitempty"` +} + +// NodeStatus represents the status of a single node in the Pacemaker cluster +type PacemakerNodeStatus struct { + // name is the name of the node + // It must be a valid string between 1 and 256 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +required + Name string `json:"name,omitempty"` + + // ipAddress is the canonical IPv4 or IPv6 address of the node + // When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). + // This excludes special addresses like unspecified, loopback, link-local, and multicast. + // When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:MinLength=2 + // +kubebuilder:validation:MaxLength=39 + // +kubebuilder:validation:XValidation:rule="isIP(self) && ip.isCanonical(self) && ip(self).isGlobalUnicast()",message="ipAddress must be a valid canonical global unicast IPv4 or IPv6 address" + // +optional + IPAddress string `json:"ipAddress,omitempty"` + + // onlineStatus indicates if the node is online or offline + // NodeOnlineStatusType can be one of the following values: + // - Online - the node is online + // - Offline - the node is offline + // When present, it must be a valid NodeOnlineStatusType. + // When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + OnlineStatus NodeOnlineStatusType `json:"onlineStatus,omitempty"` + + // mode indicates if the node is in active or standby mode + // NodeModeType can be one of the following values: + // - Active - the node is in active mode + // - Standby - the node is in standby mode + // When present, it must be a valid NodeModeType. + // When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + Mode NodeModeType `json:"mode,omitempty"` +} + +// PacemakerResourceStatus represents the status of a single resource in the Pacemaker cluster +type PacemakerResourceStatus struct { + // name is the name of the resource + // It must be a valid string between 1 and 256 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +required + Name string `json:"name,omitempty"` + + // resourceAgent is the resource agent type (e.g., "ocf:heartbeat:IPaddr2", "systemd:kubelet") + // When present, it must be a valid string between 1 and 256 characters long. + // When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +optional + ResourceAgent string `json:"resourceAgent,omitempty"` + + // role is the current role of the resource + // ResourceRoleType can be one of the following values: + // - Started - the resource is started + // - Stopped - the resource is stopped + // We don't use promoted and unpromoted, so resources in those roles would omit the role field. + // When present, it must be a valid ResourceRoleType. + // When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output. + // +optional + Role ResourceRoleType `json:"role,omitempty"` + + // activeStatus indicates if the resource is active or inactive + // ResourceActiveStatusType can be one of the following values: + // - Active - the resource is active + // - Inactive - the resource is inactive + // When present, it must be a valid ResourceActiveStatusType. + // When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + ActiveStatus ResourceActiveStatusType `json:"activeStatus,omitempty"` + + // node is the node where the resource is running + // When present, it must be a valid string between 1 and 256 characters long. + // When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +optional + Node string `json:"node,omitempty"` +} + +// PacemakerNodeHistoryEntry represents a single operation history entry from node_history +type PacemakerNodeHistoryEntry struct { + // node is the node where the operation occurred + // It must be a valid string between 1 and 256 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +required + Node string `json:"node,omitempty"` + + // resource is the resource that was operated on + // It must be a valid string between 1 and 256 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +required + Resource string `json:"resource,omitempty"` + + // operation is the operation that was performed (e.g., "monitor", "start", "stop") + // Unlike other fields, this is not an enum because while "monitor", "start" and "stop" + // are the most common, resource agents can define their own operations. + // It must be a valid string between 1 and 32 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=32 + // +required + Operation string `json:"operation,omitempty"` + + // rc is the return code from the operation + // When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. + // When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=2147483647 + // +optional + RC *int32 `json:"rc,omitempty"` + + // rcText is the human-readable return code text (e.g., "ok", "error", "not running") + // When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. + // When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=32 + // +optional + RCText string `json:"rcText,omitempty"` + + // lastRCChange is the timestamp when the RC last changed + // It must be a valid timestamp in RFC3339 format and cannot be empty. + // +kubebuilder:validation:Format=RFC3339 + // +required + LastRCChange metav1.Time `json:"lastRCChange,omitempty"` +} + +// PacemakerFencingEvent represents a single fencing event from fence history +type PacemakerFencingEvent struct { + // target is the node that was fenced + // It must be a valid string between 1 and 256 characters long and cannot be empty. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + // +required + Target string `json:"target,omitempty"` + + // action is the fencing action performed + // FencingActionType can be one of the following values: + // - reboot - the node was rebooted + // - off - the node was turned off + // - on - the node was turned on + // When present, it must be a valid FencingActionType. + // When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + Action FencingActionType `json:"action,omitempty"` + + // status is the status of the fencing operation + // FencingStatusType can be one of the following values: + // - success - the fencing event was successful + // - failed - the fencing event failed + // - pending - the fencing event is pending + // When present, it must be a valid FencingStatusType. + // When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output. + // +optional + Status FencingStatusType `json:"status,omitempty"` + + // completed is the timestamp when the fencing event was completed + // It must be a valid timestamp in RFC3339 format and cannot be empty. + // +kubebuilder:validation:Format=RFC3339 + // +required + Completed metav1.Time `json:"completed,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +openshift:compatibility-gen:level=4 + +// PacemakerClusterList contains a list of PacemakerCluster objects. +// +// Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support. +type PacemakerClusterList struct { + metav1.TypeMeta `json:",inline"` + + // metadata is the standard list's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + metav1.ListMeta `json:"metadata,omitempty"` + + // items is a list of PacemakerCluster objects. + Items []PacemakerCluster `json:"items"` +} diff --git a/etcd/v1alpha1/zz_generated.crd-manifests/0000_25_etcd_01_pacemakerclusters.crd.yaml b/etcd/v1alpha1/zz_generated.crd-manifests/0000_25_etcd_01_pacemakerclusters.crd.yaml new file mode 100644 index 00000000000..093d5ff9cdd --- /dev/null +++ b/etcd/v1alpha1/zz_generated.crd-manifests/0000_25_etcd_01_pacemakerclusters.crd.yaml @@ -0,0 +1,425 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2544 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + name: pacemakerclusters.etcd.openshift.io +spec: + group: etcd.openshift.io + names: + kind: PacemakerCluster + listKind: PacemakerClusterList + plural: pacemakerclusters + singular: pacemakercluster + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'Compatibility level 4: No compatibility is provided, the API + can change at any point for any reason. These capabilities should not be + used by applications needing long term support.' + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is an empty spec to satisfy Kubernetes API conventions. + PacemakerCluster is a status-only resource and does not use spec for configuration. + type: object + status: + description: |- + status contains the actual pacemaker cluster status information collected from the cluster. + This follows the Design Principle: Act on Deterministic Information. + When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator. + properties: + collectionError: + description: |- + collectionError contains any error encountered while collecting status + When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. + When not present, no collection errors are available and the status collection is assumed to be successful. + maxLength: 2048 + minLength: 1 + type: string + fencingHistory: + description: |- + fencingHistory provides recent fencing events + When present, it must be a list of 1 or more PacemakerFencingEvent objects. + When not present, the fencing history is not available. This is the expected status for a healthy cluster. + Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported). + items: + description: PacemakerFencingEvent represents a single fencing event + from fence history + properties: + action: + description: |- + action is the fencing action performed + FencingActionType can be one of the following values: + - reboot - the node was rebooted + - off - the node was turned off + - on - the node was turned on + When present, it must be a valid FencingActionType. + When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - reboot + - false + - true + type: string + completed: + allOf: + - format: date-time + - format: RFC3339 + description: |- + completed is the timestamp when the fencing event was completed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + status: + description: |- + status is the status of the fencing operation + FencingStatusType can be one of the following values: + - success - the fencing event was successful + - failed - the fencing event failed + - pending - the fencing event is pending + When present, it must be a valid FencingStatusType. + When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - success + - failed + - pending + type: string + target: + description: |- + target is the node that was fenced + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - completed + - target + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + lastUpdated: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastUpdated is the timestamp when this status was last updated + When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value + This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. + It is optional upon initial creation, but required once it has been set. + type: string + nodeHistory: + description: |- + nodeHistory provides recent operation history for troubleshooting + When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. + When not present, the node history is not available. This is the expected status for a healthy cluster. + Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported). + items: + description: PacemakerNodeHistoryEntry represents a single operation + history entry from node_history + properties: + lastRCChange: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastRCChange is the timestamp when the RC last changed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + node: + description: |- + node is the node where the operation occurred + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + operation: + description: |- + operation is the operation that was performed (e.g., "monitor", "start", "stop") + Unlike other fields, this is not an enum because while "monitor", "start" and "stop" + are the most common, resource agents can define their own operations. + It must be a valid string between 1 and 32 characters long and cannot be empty. + maxLength: 32 + minLength: 1 + type: string + rc: + description: |- + rc is the return code from the operation + When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. + When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + rcText: + description: |- + rcText is the human-readable return code text (e.g., "ok", "error", "not running") + When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. + When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 32 + minLength: 1 + type: string + resource: + description: |- + resource is the resource that was operated on + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - lastRCChange + - node + - operation + - resource + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + nodes: + description: |- + nodes provides detailed information about each node in the cluster + When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. + When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. + If only one node is present, this indicates that the cluster is in the process of replacing a failed node. + items: + description: NodeStatus represents the status of a single node in + the Pacemaker cluster + properties: + ipAddress: + description: |- + ipAddress is the canonical IPv4 or IPv6 address of the node + When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). + This excludes special addresses like unspecified, loopback, link-local, and multicast. + When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 39 + minLength: 2 + type: string + x-kubernetes-validations: + - message: ipAddress must be a valid canonical global unicast + IPv4 or IPv6 address + rule: isIP(self) && ip.isCanonical(self) && ip(self).isGlobalUnicast() + mode: + description: |- + mode indicates if the node is in active or standby mode + NodeModeType can be one of the following values: + - Active - the node is in active mode + - Standby - the node is in standby mode + When present, it must be a valid NodeModeType. + When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Standby + type: string + name: + description: |- + name is the name of the node + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + onlineStatus: + description: |- + onlineStatus indicates if the node is online or offline + NodeOnlineStatusType can be one of the following values: + - Online - the node is online + - Offline - the node is offline + When present, it must be a valid NodeOnlineStatusType. + When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Online + - Offline + type: string + required: + - name + type: object + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + rawXML: + description: |- + rawXML contains the raw XML output from pcs status xml command. + Kept for debugging purposes only; healthcheck should not need to parse this. + When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. + When not present, the raw XML output is not available. + maxLength: 262144 + minLength: 1 + type: string + resources: + description: |- + resources provides detailed information about each resource in the cluster + When present, it must be a list of 1 or more PacemakerResourceStatus objects. + When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. + The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. + The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + This could drift over time as Two Node Fencing matures, so this is left flexible. + items: + description: PacemakerResourceStatus represents the status of a + single resource in the Pacemaker cluster + properties: + activeStatus: + description: |- + activeStatus indicates if the resource is active or inactive + ResourceActiveStatusType can be one of the following values: + - Active - the resource is active + - Inactive - the resource is inactive + When present, it must be a valid ResourceActiveStatusType. + When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Inactive + type: string + name: + description: |- + name is the name of the resource + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + node: + description: |- + node is the node where the resource is running + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + resourceAgent: + description: |- + resourceAgent is the resource agent type (e.g., "ocf:heartbeat:IPaddr2", "systemd:kubelet") + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + role: + description: |- + role is the current role of the resource + ResourceRoleType can be one of the following values: + - Started - the resource is started + - Stopped - the resource is stopped + We don't use promoted and unpromoted, so resources in those roles would omit the role field. + When present, it must be a valid ResourceRoleType. + When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output. + enum: + - Started + - Stopped + type: string + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + summary: + description: |- + summary provides high-level counts and flags for the cluster state + TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. + When present, it must be a valid PacemakerSummary object. + When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output. + properties: + nodesOnline: + description: |- + nodesOnline is the count of online nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + nodesTotal: + description: |- + nodesTotal is the total count of configured nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + pacemakerDaemonState: + description: |- + pacemakerDaemonState indicates the state of the pacemaker daemon + PacemakerDaemonStateType can be one of the following values: + - Running - the pacemaker daemon is in the 'running' state + - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state + to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. + When present, it must be a valid PacemakerDaemonStateType. + When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Running + - KnownNotRunning + type: string + quorumStatus: + description: |- + quorumStatus indicates if the cluster has quorum + QuorumStatusType can be one of the following values: + - Quorate - the cluster has quorum + - NoQuorum - the cluster does not have quorum + When present, it must be a valid QuorumStatusType. + When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Quorate + - NoQuorum + type: string + resourcesStarted: + description: |- + resourcesStarted is the count of started resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The number could be less than 6 if the cluster is starting up or not healthy. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + resourcesTotal: + description: |- + resourcesTotal is the total count of configured resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + type: object + type: object + x-kubernetes-validations: + - message: lastUpdated must be a newer timestamp than the current value + rule: '!has(oldSelf.lastUpdated) || (has(self.lastUpdated) && self.lastUpdated + >= oldSelf.lastUpdated)' + type: object + served: true + storage: true + subresources: + status: {} diff --git a/etcd/v1alpha1/zz_generated.crd-manifests/doc.go b/etcd/v1alpha1/zz_generated.crd-manifests/doc.go new file mode 100644 index 00000000000..fbefa7bcc2c --- /dev/null +++ b/etcd/v1alpha1/zz_generated.crd-manifests/doc.go @@ -0,0 +1 @@ +package etcd_v1alpha1_crdmanifests diff --git a/etcd/v1alpha1/zz_generated.deepcopy.go b/etcd/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..f46013d2367 --- /dev/null +++ b/etcd/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,248 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by codegen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerCluster) DeepCopyInto(out *PacemakerCluster) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Spec != nil { + in, out := &in.Spec, &out.Spec + *out = new(PacemakerClusterSpec) + **out = **in + } + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = new(PacemakerClusterStatus) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerCluster. +func (in *PacemakerCluster) DeepCopy() *PacemakerCluster { + if in == nil { + return nil + } + out := new(PacemakerCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PacemakerCluster) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerClusterList) DeepCopyInto(out *PacemakerClusterList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PacemakerCluster, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerClusterList. +func (in *PacemakerClusterList) DeepCopy() *PacemakerClusterList { + if in == nil { + return nil + } + out := new(PacemakerClusterList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PacemakerClusterList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerClusterSpec) DeepCopyInto(out *PacemakerClusterSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerClusterSpec. +func (in *PacemakerClusterSpec) DeepCopy() *PacemakerClusterSpec { + if in == nil { + return nil + } + out := new(PacemakerClusterSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerClusterStatus) DeepCopyInto(out *PacemakerClusterStatus) { + *out = *in + in.LastUpdated.DeepCopyInto(&out.LastUpdated) + if in.Summary != nil { + in, out := &in.Summary, &out.Summary + *out = new(PacemakerSummary) + (*in).DeepCopyInto(*out) + } + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]PacemakerNodeStatus, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]PacemakerResourceStatus, len(*in)) + copy(*out, *in) + } + if in.NodeHistory != nil { + in, out := &in.NodeHistory, &out.NodeHistory + *out = make([]PacemakerNodeHistoryEntry, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FencingHistory != nil { + in, out := &in.FencingHistory, &out.FencingHistory + *out = make([]PacemakerFencingEvent, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerClusterStatus. +func (in *PacemakerClusterStatus) DeepCopy() *PacemakerClusterStatus { + if in == nil { + return nil + } + out := new(PacemakerClusterStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerFencingEvent) DeepCopyInto(out *PacemakerFencingEvent) { + *out = *in + in.Completed.DeepCopyInto(&out.Completed) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerFencingEvent. +func (in *PacemakerFencingEvent) DeepCopy() *PacemakerFencingEvent { + if in == nil { + return nil + } + out := new(PacemakerFencingEvent) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerNodeHistoryEntry) DeepCopyInto(out *PacemakerNodeHistoryEntry) { + *out = *in + if in.RC != nil { + in, out := &in.RC, &out.RC + *out = new(int32) + **out = **in + } + in.LastRCChange.DeepCopyInto(&out.LastRCChange) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerNodeHistoryEntry. +func (in *PacemakerNodeHistoryEntry) DeepCopy() *PacemakerNodeHistoryEntry { + if in == nil { + return nil + } + out := new(PacemakerNodeHistoryEntry) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerNodeStatus) DeepCopyInto(out *PacemakerNodeStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerNodeStatus. +func (in *PacemakerNodeStatus) DeepCopy() *PacemakerNodeStatus { + if in == nil { + return nil + } + out := new(PacemakerNodeStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerResourceStatus) DeepCopyInto(out *PacemakerResourceStatus) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerResourceStatus. +func (in *PacemakerResourceStatus) DeepCopy() *PacemakerResourceStatus { + if in == nil { + return nil + } + out := new(PacemakerResourceStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PacemakerSummary) DeepCopyInto(out *PacemakerSummary) { + *out = *in + if in.NodesOnline != nil { + in, out := &in.NodesOnline, &out.NodesOnline + *out = new(int32) + **out = **in + } + if in.NodesTotal != nil { + in, out := &in.NodesTotal, &out.NodesTotal + *out = new(int32) + **out = **in + } + if in.ResourcesStarted != nil { + in, out := &in.ResourcesStarted, &out.ResourcesStarted + *out = new(int32) + **out = **in + } + if in.ResourcesTotal != nil { + in, out := &in.ResourcesTotal, &out.ResourcesTotal + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PacemakerSummary. +func (in *PacemakerSummary) DeepCopy() *PacemakerSummary { + if in == nil { + return nil + } + out := new(PacemakerSummary) + in.DeepCopyInto(out) + return out +} diff --git a/etcd/v1alpha1/zz_generated.featuregated-crd-manifests.yaml b/etcd/v1alpha1/zz_generated.featuregated-crd-manifests.yaml new file mode 100644 index 00000000000..f0aabdce11f --- /dev/null +++ b/etcd/v1alpha1/zz_generated.featuregated-crd-manifests.yaml @@ -0,0 +1,21 @@ +pacemakerclusters.etcd.openshift.io: + Annotations: {} + ApprovedPRNumber: https://github.com/openshift/api/pull/2544 + CRDName: pacemakerclusters.etcd.openshift.io + Capability: "" + Category: "" + FeatureGates: [] + FilenameOperatorName: etcd + FilenameOperatorOrdering: "01" + FilenameRunLevel: "0000_25" + GroupName: etcd.openshift.io + HasStatus: true + KindName: PacemakerCluster + Labels: {} + PluralName: pacemakerclusters + PrinterColumns: [] + Scope: Cluster + ShortNames: null + TopLevelFeatureGates: [] + Version: v1alpha1 + diff --git a/etcd/v1alpha1/zz_generated.featuregated-crd-manifests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml b/etcd/v1alpha1/zz_generated.featuregated-crd-manifests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml new file mode 100644 index 00000000000..b598a77123d --- /dev/null +++ b/etcd/v1alpha1/zz_generated.featuregated-crd-manifests/pacemakerclusters.etcd.openshift.io/AAA_ungated.yaml @@ -0,0 +1,426 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2544 + api.openshift.io/filename-cvo-runlevel: "0000_25" + api.openshift.io/filename-operator: etcd + api.openshift.io/filename-ordering: "01" + feature-gate.release.openshift.io/: "true" + name: pacemakerclusters.etcd.openshift.io +spec: + group: etcd.openshift.io + names: + kind: PacemakerCluster + listKind: PacemakerClusterList + plural: pacemakerclusters + singular: pacemakercluster + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'Compatibility level 4: No compatibility is provided, the API + can change at any point for any reason. These capabilities should not be + used by applications needing long term support.' + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is an empty spec to satisfy Kubernetes API conventions. + PacemakerCluster is a status-only resource and does not use spec for configuration. + type: object + status: + description: |- + status contains the actual pacemaker cluster status information collected from the cluster. + This follows the Design Principle: Act on Deterministic Information. + When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator. + properties: + collectionError: + description: |- + collectionError contains any error encountered while collecting status + When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. + When not present, no collection errors are available and the status collection is assumed to be successful. + maxLength: 2048 + minLength: 1 + type: string + fencingHistory: + description: |- + fencingHistory provides recent fencing events + When present, it must be a list of 1 or more PacemakerFencingEvent objects. + When not present, the fencing history is not available. This is the expected status for a healthy cluster. + Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported). + items: + description: PacemakerFencingEvent represents a single fencing event + from fence history + properties: + action: + description: |- + action is the fencing action performed + FencingActionType can be one of the following values: + - reboot - the node was rebooted + - off - the node was turned off + - on - the node was turned on + When present, it must be a valid FencingActionType. + When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - reboot + - false + - true + type: string + completed: + allOf: + - format: date-time + - format: RFC3339 + description: |- + completed is the timestamp when the fencing event was completed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + status: + description: |- + status is the status of the fencing operation + FencingStatusType can be one of the following values: + - success - the fencing event was successful + - failed - the fencing event failed + - pending - the fencing event is pending + When present, it must be a valid FencingStatusType. + When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - success + - failed + - pending + type: string + target: + description: |- + target is the node that was fenced + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - completed + - target + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + lastUpdated: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastUpdated is the timestamp when this status was last updated + When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value + This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. + It is optional upon initial creation, but required once it has been set. + type: string + nodeHistory: + description: |- + nodeHistory provides recent operation history for troubleshooting + When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. + When not present, the node history is not available. This is the expected status for a healthy cluster. + Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported). + items: + description: PacemakerNodeHistoryEntry represents a single operation + history entry from node_history + properties: + lastRCChange: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastRCChange is the timestamp when the RC last changed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + node: + description: |- + node is the node where the operation occurred + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + operation: + description: |- + operation is the operation that was performed (e.g., "monitor", "start", "stop") + Unlike other fields, this is not an enum because while "monitor", "start" and "stop" + are the most common, resource agents can define their own operations. + It must be a valid string between 1 and 32 characters long and cannot be empty. + maxLength: 32 + minLength: 1 + type: string + rc: + description: |- + rc is the return code from the operation + When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. + When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + rcText: + description: |- + rcText is the human-readable return code text (e.g., "ok", "error", "not running") + When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. + When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 32 + minLength: 1 + type: string + resource: + description: |- + resource is the resource that was operated on + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - lastRCChange + - node + - operation + - resource + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + nodes: + description: |- + nodes provides detailed information about each node in the cluster + When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. + When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. + If only one node is present, this indicates that the cluster is in the process of replacing a failed node. + items: + description: NodeStatus represents the status of a single node in + the Pacemaker cluster + properties: + ipAddress: + description: |- + ipAddress is the canonical IPv4 or IPv6 address of the node + When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). + This excludes special addresses like unspecified, loopback, link-local, and multicast. + When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 39 + minLength: 2 + type: string + x-kubernetes-validations: + - message: ipAddress must be a valid canonical global unicast + IPv4 or IPv6 address + rule: isIP(self) && ip.isCanonical(self) && ip(self).isGlobalUnicast() + mode: + description: |- + mode indicates if the node is in active or standby mode + NodeModeType can be one of the following values: + - Active - the node is in active mode + - Standby - the node is in standby mode + When present, it must be a valid NodeModeType. + When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Standby + type: string + name: + description: |- + name is the name of the node + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + onlineStatus: + description: |- + onlineStatus indicates if the node is online or offline + NodeOnlineStatusType can be one of the following values: + - Online - the node is online + - Offline - the node is offline + When present, it must be a valid NodeOnlineStatusType. + When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Online + - Offline + type: string + required: + - name + type: object + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + rawXML: + description: |- + rawXML contains the raw XML output from pcs status xml command. + Kept for debugging purposes only; healthcheck should not need to parse this. + When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. + When not present, the raw XML output is not available. + maxLength: 262144 + minLength: 1 + type: string + resources: + description: |- + resources provides detailed information about each resource in the cluster + When present, it must be a list of 1 or more PacemakerResourceStatus objects. + When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. + The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. + The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + This could drift over time as Two Node Fencing matures, so this is left flexible. + items: + description: PacemakerResourceStatus represents the status of a + single resource in the Pacemaker cluster + properties: + activeStatus: + description: |- + activeStatus indicates if the resource is active or inactive + ResourceActiveStatusType can be one of the following values: + - Active - the resource is active + - Inactive - the resource is inactive + When present, it must be a valid ResourceActiveStatusType. + When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Inactive + type: string + name: + description: |- + name is the name of the resource + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + node: + description: |- + node is the node where the resource is running + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + resourceAgent: + description: |- + resourceAgent is the resource agent type (e.g., "ocf:heartbeat:IPaddr2", "systemd:kubelet") + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + role: + description: |- + role is the current role of the resource + ResourceRoleType can be one of the following values: + - Started - the resource is started + - Stopped - the resource is stopped + We don't use promoted and unpromoted, so resources in those roles would omit the role field. + When present, it must be a valid ResourceRoleType. + When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output. + enum: + - Started + - Stopped + type: string + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + summary: + description: |- + summary provides high-level counts and flags for the cluster state + TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. + When present, it must be a valid PacemakerSummary object. + When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output. + properties: + nodesOnline: + description: |- + nodesOnline is the count of online nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + nodesTotal: + description: |- + nodesTotal is the total count of configured nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + pacemakerDaemonState: + description: |- + pacemakerDaemonState indicates the state of the pacemaker daemon + PacemakerDaemonStateType can be one of the following values: + - Running - the pacemaker daemon is in the 'running' state + - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state + to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. + When present, it must be a valid PacemakerDaemonStateType. + When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Running + - KnownNotRunning + type: string + quorumStatus: + description: |- + quorumStatus indicates if the cluster has quorum + QuorumStatusType can be one of the following values: + - Quorate - the cluster has quorum + - NoQuorum - the cluster does not have quorum + When present, it must be a valid QuorumStatusType. + When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Quorate + - NoQuorum + type: string + resourcesStarted: + description: |- + resourcesStarted is the count of started resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The number could be less than 6 if the cluster is starting up or not healthy. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + resourcesTotal: + description: |- + resourcesTotal is the total count of configured resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + type: object + type: object + x-kubernetes-validations: + - message: lastUpdated must be a newer timestamp than the current value + rule: '!has(oldSelf.lastUpdated) || (has(self.lastUpdated) && self.lastUpdated + >= oldSelf.lastUpdated)' + type: object + served: true + storage: true + subresources: + status: {} diff --git a/etcd/v1alpha1/zz_generated.swagger_doc_generated.go b/etcd/v1alpha1/zz_generated.swagger_doc_generated.go new file mode 100644 index 00000000000..403d2035231 --- /dev/null +++ b/etcd/v1alpha1/zz_generated.swagger_doc_generated.go @@ -0,0 +1,124 @@ +package v1alpha1 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_PacemakerCluster = map[string]string{ + "": "Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + "metadata": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "spec": "spec is an empty spec to satisfy Kubernetes API conventions. PacemakerCluster is a status-only resource and does not use spec for configuration.", + "status": "status contains the actual pacemaker cluster status information collected from the cluster. This follows the Design Principle: Act on Deterministic Information. When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator.", +} + +func (PacemakerCluster) SwaggerDoc() map[string]string { + return map_PacemakerCluster +} + +var map_PacemakerClusterList = map[string]string{ + "": "PacemakerClusterList contains a list of PacemakerCluster objects.\n\nCompatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + "metadata": "metadata is the standard list's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "items": "items is a list of PacemakerCluster objects.", +} + +func (PacemakerClusterList) SwaggerDoc() map[string]string { + return map_PacemakerClusterList +} + +var map_PacemakerClusterSpec = map[string]string{ + "": "PacemakerClusterSpec is an empty spec as PacemakerCluster is a status-only resource", +} + +func (PacemakerClusterSpec) SwaggerDoc() map[string]string { + return map_PacemakerClusterSpec +} + +var map_PacemakerClusterStatus = map[string]string{ + "": "PacemakerClusterStatus contains the actual pacemaker cluster status information As part of validating the status object, we need to ensure that the lastUpdated timestamp is always newer than the current value. We allow the lastUpdated timestamp to be empty on initial creation, but it is required once it has been set.", + "lastUpdated": "lastUpdated is the timestamp when this status was last updated When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. It is optional upon initial creation, but required once it has been set.", + "rawXML": "rawXML contains the raw XML output from pcs status xml command. Kept for debugging purposes only; healthcheck should not need to parse this. When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. When not present, the raw XML output is not available.", + "collectionError": "collectionError contains any error encountered while collecting status When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. When not present, no collection errors are available and the status collection is assumed to be successful.", + "summary": "summary provides high-level counts and flags for the cluster state TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. When present, it must be a valid PacemakerSummary object. When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output.", + "nodes": "nodes provides detailed information about each node in the cluster When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. If only one node is present, this indicates that the cluster is in the process of replacing a failed node.", + "resources": "resources provides detailed information about each resource in the cluster When present, it must be a list of 1 or more PacemakerResourceStatus objects. When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. This could drift over time as Two Node Fencing matures, so this is left flexible.", + "nodeHistory": "nodeHistory provides recent operation history for troubleshooting When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. When not present, the node history is not available. This is the expected status for a healthy cluster. Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported).", + "fencingHistory": "fencingHistory provides recent fencing events When present, it must be a list of 1 or more PacemakerFencingEvent objects. When not present, the fencing history is not available. This is the expected status for a healthy cluster. Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported).", +} + +func (PacemakerClusterStatus) SwaggerDoc() map[string]string { + return map_PacemakerClusterStatus +} + +var map_PacemakerFencingEvent = map[string]string{ + "": "PacemakerFencingEvent represents a single fencing event from fence history", + "target": "target is the node that was fenced It must be a valid string between 1 and 256 characters long and cannot be empty.", + "action": "action is the fencing action performed FencingActionType can be one of the following values: - reboot - the node was rebooted - off - the node was turned off - on - the node was turned on When present, it must be a valid FencingActionType. When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output.", + "status": "status is the status of the fencing operation FencingStatusType can be one of the following values: - success - the fencing event was successful - failed - the fencing event failed - pending - the fencing event is pending When present, it must be a valid FencingStatusType. When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "completed": "completed is the timestamp when the fencing event was completed It must be a valid timestamp in RFC3339 format and cannot be empty.", +} + +func (PacemakerFencingEvent) SwaggerDoc() map[string]string { + return map_PacemakerFencingEvent +} + +var map_PacemakerNodeHistoryEntry = map[string]string{ + "": "PacemakerNodeHistoryEntry represents a single operation history entry from node_history", + "node": "node is the node where the operation occurred It must be a valid string between 1 and 256 characters long and cannot be empty.", + "resource": "resource is the resource that was operated on It must be a valid string between 1 and 256 characters long and cannot be empty.", + "operation": "operation is the operation that was performed (e.g., \"monitor\", \"start\", \"stop\") Unlike other fields, this is not an enum because while \"monitor\", \"start\" and \"stop\" are the most common, resource agents can define their own operations. It must be a valid string between 1 and 32 characters long and cannot be empty.", + "rc": "rc is the return code from the operation When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output.", + "rcText": "rcText is the human-readable return code text (e.g., \"ok\", \"error\", \"not running\") When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output.", + "lastRCChange": "lastRCChange is the timestamp when the RC last changed It must be a valid timestamp in RFC3339 format and cannot be empty.", +} + +func (PacemakerNodeHistoryEntry) SwaggerDoc() map[string]string { + return map_PacemakerNodeHistoryEntry +} + +var map_PacemakerNodeStatus = map[string]string{ + "": "NodeStatus represents the status of a single node in the Pacemaker cluster", + "name": "name is the name of the node It must be a valid string between 1 and 256 characters long and cannot be empty.", + "ipAddress": "ipAddress is the canonical IPv4 or IPv6 address of the node When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). This excludes special addresses like unspecified, loopback, link-local, and multicast. When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output.", + "onlineStatus": "onlineStatus indicates if the node is online or offline NodeOnlineStatusType can be one of the following values: - Online - the node is online - Offline - the node is offline When present, it must be a valid NodeOnlineStatusType. When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "mode": "mode indicates if the node is in active or standby mode NodeModeType can be one of the following values: - Active - the node is in active mode - Standby - the node is in standby mode When present, it must be a valid NodeModeType. When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output.", +} + +func (PacemakerNodeStatus) SwaggerDoc() map[string]string { + return map_PacemakerNodeStatus +} + +var map_PacemakerResourceStatus = map[string]string{ + "": "PacemakerResourceStatus represents the status of a single resource in the Pacemaker cluster", + "name": "name is the name of the resource It must be a valid string between 1 and 256 characters long and cannot be empty.", + "resourceAgent": "resourceAgent is the resource agent type (e.g., \"ocf:heartbeat:IPaddr2\", \"systemd:kubelet\") When present, it must be a valid string between 1 and 256 characters long. When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output.", + "role": "role is the current role of the resource ResourceRoleType can be one of the following values: - Started - the resource is started - Stopped - the resource is stopped We don't use promoted and unpromoted, so resources in those roles would omit the role field. When present, it must be a valid ResourceRoleType. When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output.", + "activeStatus": "activeStatus indicates if the resource is active or inactive ResourceActiveStatusType can be one of the following values: - Active - the resource is active - Inactive - the resource is inactive When present, it must be a valid ResourceActiveStatusType. When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "node": "node is the node where the resource is running When present, it must be a valid string between 1 and 256 characters long. When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output.", +} + +func (PacemakerResourceStatus) SwaggerDoc() map[string]string { + return map_PacemakerResourceStatus +} + +var map_PacemakerSummary = map[string]string{ + "": "PacemakerSummary provides a high-level summary of cluster state", + "pacemakerDaemonState": "pacemakerDaemonState indicates the state of the pacemaker daemon PacemakerDaemonStateType can be one of the following values: - Running - the pacemaker daemon is in the 'running' state - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state\n to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc.\nWhen present, it must be a valid PacemakerDaemonStateType. When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output.", + "quorumStatus": "quorumStatus indicates if the cluster has quorum QuorumStatusType can be one of the following values: - Quorate - the cluster has quorum - NoQuorum - the cluster does not have quorum When present, it must be a valid QuorumStatusType. When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "nodesOnline": "nodesOnline is the count of online nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "nodesTotal": "nodesTotal is the total count of configured nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "resourcesStarted": "resourcesStarted is the count of started resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The number could be less than 6 if the cluster is starting up or not healthy. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "resourcesTotal": "resourcesTotal is the total count of configured resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output.", +} + +func (PacemakerSummary) SwaggerDoc() map[string]string { + return map_PacemakerSummary +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/hack/update-payload-crds.sh b/hack/update-payload-crds.sh index 462783369a8..bf0d6a7e3c5 100755 --- a/hack/update-payload-crds.sh +++ b/hack/update-payload-crds.sh @@ -30,6 +30,7 @@ crd_globs="\ operator/v1/zz_generated.crd-manifests/*_csi-driver_01_clustercsidrivers*.crd.yaml insights/v1alpha2/zz_generated.crd-manifests/0000_10_insights_01_datagathers*.crd.yaml config/v1alpha2/zz_generated.crd-manifests/0000_10_config-operator_01_insightsdatagathers*.crd.yaml + etcd/v1alpha1/zz_generated.crd-manifests/0000_25_etcd_01_pacemakerclusters*.crd.yaml " # To allow the crd_globs to be sourced in the verify script, diff --git a/install.go b/install.go index cc911500091..e7b72886567 100644 --- a/install.go +++ b/install.go @@ -54,6 +54,7 @@ import ( "github.com/openshift/api/cloudnetwork" "github.com/openshift/api/config" "github.com/openshift/api/console" + "github.com/openshift/api/etcd" "github.com/openshift/api/helm" "github.com/openshift/api/image" "github.com/openshift/api/imageregistry" @@ -89,6 +90,7 @@ var ( build.Install, config.Install, console.Install, + etcd.Install, helm.Install, image.Install, imageregistry.Install, diff --git a/openapi/generated_openapi/zz_generated.openapi.go b/openapi/generated_openapi/zz_generated.openapi.go index 74dea0a305e..edc4bce37f4 100644 --- a/openapi/generated_openapi/zz_generated.openapi.go +++ b/openapi/generated_openapi/zz_generated.openapi.go @@ -531,6 +531,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/openshift/api/console/v1.ConsoleYAMLSampleSpec": schema_openshift_api_console_v1_ConsoleYAMLSampleSpec(ref), "github.com/openshift/api/console/v1.Link": schema_openshift_api_console_v1_Link(ref), "github.com/openshift/api/console/v1.NamespaceDashboardSpec": schema_openshift_api_console_v1_NamespaceDashboardSpec(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerCluster": schema_openshift_api_etcd_v1alpha1_PacemakerCluster(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerClusterList": schema_openshift_api_etcd_v1alpha1_PacemakerClusterList(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerClusterSpec": schema_openshift_api_etcd_v1alpha1_PacemakerClusterSpec(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerClusterStatus": schema_openshift_api_etcd_v1alpha1_PacemakerClusterStatus(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerFencingEvent": schema_openshift_api_etcd_v1alpha1_PacemakerFencingEvent(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerNodeHistoryEntry": schema_openshift_api_etcd_v1alpha1_PacemakerNodeHistoryEntry(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerNodeStatus": schema_openshift_api_etcd_v1alpha1_PacemakerNodeStatus(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerResourceStatus": schema_openshift_api_etcd_v1alpha1_PacemakerResourceStatus(ref), + "github.com/openshift/api/etcd/v1alpha1.PacemakerSummary": schema_openshift_api_etcd_v1alpha1_PacemakerSummary(ref), "github.com/openshift/api/example/v1.CELUnion": schema_openshift_api_example_v1_CELUnion(ref), "github.com/openshift/api/example/v1.EvolvingUnion": schema_openshift_api_example_v1_EvolvingUnion(ref), "github.com/openshift/api/example/v1.StableConfigType": schema_openshift_api_example_v1_StableConfigType(ref), @@ -25682,6 +25691,479 @@ func schema_openshift_api_console_v1_NamespaceDashboardSpec(ref common.Reference } } +func schema_openshift_api_etcd_v1alpha1_PacemakerCluster(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec is an empty spec to satisfy Kubernetes API conventions. PacemakerCluster is a status-only resource and does not use spec for configuration.", + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerClusterSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status contains the actual pacemaker cluster status information collected from the cluster. This follows the Design Principle: Act on Deterministic Information. When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator.", + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerClusterStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/openshift/api/etcd/v1alpha1.PacemakerClusterSpec", "github.com/openshift/api/etcd/v1alpha1.PacemakerClusterStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerClusterList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerClusterList contains a list of PacemakerCluster objects.\n\nCompatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "metadata is the standard list's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items is a list of PacemakerCluster objects.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerCluster"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/openshift/api/etcd/v1alpha1.PacemakerCluster", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerClusterSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerClusterSpec is an empty spec as PacemakerCluster is a status-only resource", + Type: []string{"object"}, + }, + }, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerClusterStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerClusterStatus contains the actual pacemaker cluster status information As part of validating the status object, we need to ensure that the lastUpdated timestamp is always newer than the current value. We allow the lastUpdated timestamp to be empty on initial creation, but it is required once it has been set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lastUpdated": { + SchemaProps: spec.SchemaProps{ + Description: "lastUpdated is the timestamp when this status was last updated When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. It is optional upon initial creation, but required once it has been set.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "rawXML": { + SchemaProps: spec.SchemaProps{ + Description: "rawXML contains the raw XML output from pcs status xml command. Kept for debugging purposes only; healthcheck should not need to parse this. When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. When not present, the raw XML output is not available.", + Type: []string{"string"}, + Format: "", + }, + }, + "collectionError": { + SchemaProps: spec.SchemaProps{ + Description: "collectionError contains any error encountered while collecting status When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. When not present, no collection errors are available and the status collection is assumed to be successful.", + Type: []string{"string"}, + Format: "", + }, + }, + "summary": { + SchemaProps: spec.SchemaProps{ + Description: "summary provides high-level counts and flags for the cluster state TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. When present, it must be a valid PacemakerSummary object. When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output.", + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerSummary"), + }, + }, + "nodes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "nodes provides detailed information about each node in the cluster When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. If only one node is present, this indicates that the cluster is in the process of replacing a failed node.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerNodeStatus"), + }, + }, + }, + }, + }, + "resources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "resources provides detailed information about each resource in the cluster When present, it must be a list of 1 or more PacemakerResourceStatus objects. When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. This could drift over time as Two Node Fencing matures, so this is left flexible.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerResourceStatus"), + }, + }, + }, + }, + }, + "nodeHistory": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "nodeHistory provides recent operation history for troubleshooting When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. When not present, the node history is not available. This is the expected status for a healthy cluster. Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerNodeHistoryEntry"), + }, + }, + }, + }, + }, + "fencingHistory": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "fencingHistory provides recent fencing events When present, it must be a list of 1 or more PacemakerFencingEvent objects. When not present, the fencing history is not available. This is the expected status for a healthy cluster. Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/openshift/api/etcd/v1alpha1.PacemakerFencingEvent"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/openshift/api/etcd/v1alpha1.PacemakerFencingEvent", "github.com/openshift/api/etcd/v1alpha1.PacemakerNodeHistoryEntry", "github.com/openshift/api/etcd/v1alpha1.PacemakerNodeStatus", "github.com/openshift/api/etcd/v1alpha1.PacemakerResourceStatus", "github.com/openshift/api/etcd/v1alpha1.PacemakerSummary", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerFencingEvent(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerFencingEvent represents a single fencing event from fence history", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "target": { + SchemaProps: spec.SchemaProps{ + Description: "target is the node that was fenced It must be a valid string between 1 and 256 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "action": { + SchemaProps: spec.SchemaProps{ + Description: "action is the fencing action performed FencingActionType can be one of the following values: - reboot - the node was rebooted - off - the node was turned off - on - the node was turned on When present, it must be a valid FencingActionType. When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status is the status of the fencing operation FencingStatusType can be one of the following values: - success - the fencing event was successful - failed - the fencing event failed - pending - the fencing event is pending When present, it must be a valid FencingStatusType. When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "completed": { + SchemaProps: spec.SchemaProps{ + Description: "completed is the timestamp when the fencing event was completed It must be a valid timestamp in RFC3339 format and cannot be empty.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + Required: []string{"target", "completed"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerNodeHistoryEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerNodeHistoryEntry represents a single operation history entry from node_history", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "node": { + SchemaProps: spec.SchemaProps{ + Description: "node is the node where the operation occurred It must be a valid string between 1 and 256 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "resource is the resource that was operated on It must be a valid string between 1 and 256 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "operation": { + SchemaProps: spec.SchemaProps{ + Description: "operation is the operation that was performed (e.g., \"monitor\", \"start\", \"stop\") Unlike other fields, this is not an enum because while \"monitor\", \"start\" and \"stop\" are the most common, resource agents can define their own operations. It must be a valid string between 1 and 32 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "rc": { + SchemaProps: spec.SchemaProps{ + Description: "rc is the return code from the operation When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "rcText": { + SchemaProps: spec.SchemaProps{ + Description: "rcText is the human-readable return code text (e.g., \"ok\", \"error\", \"not running\") When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "lastRCChange": { + SchemaProps: spec.SchemaProps{ + Description: "lastRCChange is the timestamp when the RC last changed It must be a valid timestamp in RFC3339 format and cannot be empty.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + Required: []string{"node", "resource", "operation", "lastRCChange"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerNodeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeStatus represents the status of a single node in the Pacemaker cluster", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the node It must be a valid string between 1 and 256 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "ipAddress": { + SchemaProps: spec.SchemaProps{ + Description: "ipAddress is the canonical IPv4 or IPv6 address of the node When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). This excludes special addresses like unspecified, loopback, link-local, and multicast. When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "onlineStatus": { + SchemaProps: spec.SchemaProps{ + Description: "onlineStatus indicates if the node is online or offline NodeOnlineStatusType can be one of the following values: - Online - the node is online - Offline - the node is offline When present, it must be a valid NodeOnlineStatusType. When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "mode": { + SchemaProps: spec.SchemaProps{ + Description: "mode indicates if the node is in active or standby mode NodeModeType can be one of the following values: - Active - the node is in active mode - Standby - the node is in standby mode When present, it must be a valid NodeModeType. When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerResourceStatus represents the status of a single resource in the Pacemaker cluster", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the resource It must be a valid string between 1 and 256 characters long and cannot be empty.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceAgent": { + SchemaProps: spec.SchemaProps{ + Description: "resourceAgent is the resource agent type (e.g., \"ocf:heartbeat:IPaddr2\", \"systemd:kubelet\") When present, it must be a valid string between 1 and 256 characters long. When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Description: "role is the current role of the resource ResourceRoleType can be one of the following values: - Started - the resource is started - Stopped - the resource is stopped We don't use promoted and unpromoted, so resources in those roles would omit the role field. When present, it must be a valid ResourceRoleType. When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "activeStatus": { + SchemaProps: spec.SchemaProps{ + Description: "activeStatus indicates if the resource is active or inactive ResourceActiveStatusType can be one of the following values: - Active - the resource is active - Inactive - the resource is inactive When present, it must be a valid ResourceActiveStatusType. When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "node": { + SchemaProps: spec.SchemaProps{ + Description: "node is the node where the resource is running When present, it must be a valid string between 1 and 256 characters long. When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_openshift_api_etcd_v1alpha1_PacemakerSummary(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PacemakerSummary provides a high-level summary of cluster state", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "pacemakerDaemonState": { + SchemaProps: spec.SchemaProps{ + Description: "pacemakerDaemonState indicates the state of the pacemaker daemon PacemakerDaemonStateType can be one of the following values: - Running - the pacemaker daemon is in the 'running' state - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state\n to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc.\nWhen present, it must be a valid PacemakerDaemonStateType. When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "quorumStatus": { + SchemaProps: spec.SchemaProps{ + Description: "quorumStatus indicates if the cluster has quorum QuorumStatusType can be one of the following values: - Quorate - the cluster has quorum - NoQuorum - the cluster does not have quorum When present, it must be a valid QuorumStatusType. When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"string"}, + Format: "", + }, + }, + "nodesOnline": { + SchemaProps: spec.SchemaProps{ + Description: "nodesOnline is the count of online nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "nodesTotal": { + SchemaProps: spec.SchemaProps{ + Description: "nodesTotal is the total count of configured nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "resourcesStarted": { + SchemaProps: spec.SchemaProps{ + Description: "resourcesStarted is the count of started resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The number could be less than 6 if the cluster is starting up or not healthy. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "resourcesTotal": { + SchemaProps: spec.SchemaProps{ + Description: "resourcesTotal is the total count of configured resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + func schema_openshift_api_example_v1_CELUnion(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/openapi/openapi.json b/openapi/openapi.json index 4e8a345de60..35dd557c038 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -14224,6 +14224,276 @@ } } }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerCluster": { + "description": "Compatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "metadata is the standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + }, + "spec": { + "description": "spec is an empty spec to satisfy Kubernetes API conventions. PacemakerCluster is a status-only resource and does not use spec for configuration.", + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerClusterSpec" + }, + "status": { + "description": "status contains the actual pacemaker cluster status information collected from the cluster. This follows the Design Principle: Act on Deterministic Information. When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator.", + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerClusterStatus" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerClusterList": { + "description": "PacemakerClusterList contains a list of PacemakerCluster objects.\n\nCompatibility level 4: No compatibility is provided, the API can change at any point for any reason. These capabilities should not be used by applications needing long term support.", + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "items is a list of PacemakerCluster objects.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerCluster" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "metadata is the standard list's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "default": {}, + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerClusterSpec": { + "description": "PacemakerClusterSpec is an empty spec as PacemakerCluster is a status-only resource", + "type": "object" + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerClusterStatus": { + "description": "PacemakerClusterStatus contains the actual pacemaker cluster status information As part of validating the status object, we need to ensure that the lastUpdated timestamp is always newer than the current value. We allow the lastUpdated timestamp to be empty on initial creation, but it is required once it has been set.", + "type": "object", + "properties": { + "collectionError": { + "description": "collectionError contains any error encountered while collecting status When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. When not present, no collection errors are available and the status collection is assumed to be successful.", + "type": "string" + }, + "fencingHistory": { + "description": "fencingHistory provides recent fencing events When present, it must be a list of 1 or more PacemakerFencingEvent objects. When not present, the fencing history is not available. This is the expected status for a healthy cluster. Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported).", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerFencingEvent" + }, + "x-kubernetes-list-type": "atomic" + }, + "lastUpdated": { + "description": "lastUpdated is the timestamp when this status was last updated When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. It is optional upon initial creation, but required once it has been set.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "nodeHistory": { + "description": "nodeHistory provides recent operation history for troubleshooting When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. When not present, the node history is not available. This is the expected status for a healthy cluster. Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster is presented as a recorded event. The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported).", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerNodeHistoryEntry" + }, + "x-kubernetes-list-type": "atomic" + }, + "nodes": { + "description": "nodes provides detailed information about each node in the cluster When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. If only one node is present, this indicates that the cluster is in the process of replacing a failed node.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerNodeStatus" + }, + "x-kubernetes-list-type": "atomic" + }, + "rawXML": { + "description": "rawXML contains the raw XML output from pcs status xml command. Kept for debugging purposes only; healthcheck should not need to parse this. When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. When not present, the raw XML output is not available.", + "type": "string" + }, + "resources": { + "description": "resources provides detailed information about each resource in the cluster When present, it must be a list of 1 or more PacemakerResourceStatus objects. When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. This could drift over time as Two Node Fencing matures, so this is left flexible.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerResourceStatus" + }, + "x-kubernetes-list-type": "atomic" + }, + "summary": { + "description": "summary provides high-level counts and flags for the cluster state TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. When present, it must be a valid PacemakerSummary object. When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output.", + "$ref": "#/definitions/com.github.openshift.api.etcd.v1alpha1.PacemakerSummary" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerFencingEvent": { + "description": "PacemakerFencingEvent represents a single fencing event from fence history", + "type": "object", + "required": [ + "target", + "completed" + ], + "properties": { + "action": { + "description": "action is the fencing action performed FencingActionType can be one of the following values: - reboot - the node was rebooted - off - the node was turned off - on - the node was turned on When present, it must be a valid FencingActionType. When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "completed": { + "description": "completed is the timestamp when the fencing event was completed It must be a valid timestamp in RFC3339 format and cannot be empty.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "status": { + "description": "status is the status of the fencing operation FencingStatusType can be one of the following values: - success - the fencing event was successful - failed - the fencing event failed - pending - the fencing event is pending When present, it must be a valid FencingStatusType. When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "target": { + "description": "target is the node that was fenced It must be a valid string between 1 and 256 characters long and cannot be empty.", + "type": "string" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerNodeHistoryEntry": { + "description": "PacemakerNodeHistoryEntry represents a single operation history entry from node_history", + "type": "object", + "required": [ + "node", + "resource", + "operation", + "lastRCChange" + ], + "properties": { + "lastRCChange": { + "description": "lastRCChange is the timestamp when the RC last changed It must be a valid timestamp in RFC3339 format and cannot be empty.", + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time" + }, + "node": { + "description": "node is the node where the operation occurred It must be a valid string between 1 and 256 characters long and cannot be empty.", + "type": "string" + }, + "operation": { + "description": "operation is the operation that was performed (e.g., \"monitor\", \"start\", \"stop\") Unlike other fields, this is not an enum because while \"monitor\", \"start\" and \"stop\" are the most common, resource agents can define their own operations. It must be a valid string between 1 and 32 characters long and cannot be empty.", + "type": "string" + }, + "rc": { + "description": "rc is the return code from the operation When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "integer", + "format": "int32" + }, + "rcText": { + "description": "rcText is the human-readable return code text (e.g., \"ok\", \"error\", \"not running\") When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "resource": { + "description": "resource is the resource that was operated on It must be a valid string between 1 and 256 characters long and cannot be empty.", + "type": "string" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerNodeStatus": { + "description": "NodeStatus represents the status of a single node in the Pacemaker cluster", + "type": "object", + "required": [ + "name" + ], + "properties": { + "ipAddress": { + "description": "ipAddress is the canonical IPv4 or IPv6 address of the node When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). This excludes special addresses like unspecified, loopback, link-local, and multicast. When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "mode": { + "description": "mode indicates if the node is in active or standby mode NodeModeType can be one of the following values: - Active - the node is in active mode - Standby - the node is in standby mode When present, it must be a valid NodeModeType. When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "name": { + "description": "name is the name of the node It must be a valid string between 1 and 256 characters long and cannot be empty.", + "type": "string" + }, + "onlineStatus": { + "description": "onlineStatus indicates if the node is online or offline NodeOnlineStatusType can be one of the following values: - Online - the node is online - Offline - the node is offline When present, it must be a valid NodeOnlineStatusType. When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerResourceStatus": { + "description": "PacemakerResourceStatus represents the status of a single resource in the Pacemaker cluster", + "type": "object", + "required": [ + "name" + ], + "properties": { + "activeStatus": { + "description": "activeStatus indicates if the resource is active or inactive ResourceActiveStatusType can be one of the following values: - Active - the resource is active - Inactive - the resource is inactive When present, it must be a valid ResourceActiveStatusType. When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "name": { + "description": "name is the name of the resource It must be a valid string between 1 and 256 characters long and cannot be empty.", + "type": "string" + }, + "node": { + "description": "node is the node where the resource is running When present, it must be a valid string between 1 and 256 characters long. When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output.", + "type": "string" + }, + "resourceAgent": { + "description": "resourceAgent is the resource agent type (e.g., \"ocf:heartbeat:IPaddr2\", \"systemd:kubelet\") When present, it must be a valid string between 1 and 256 characters long. When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "role": { + "description": "role is the current role of the resource ResourceRoleType can be one of the following values: - Started - the resource is started - Stopped - the resource is stopped We don't use promoted and unpromoted, so resources in those roles would omit the role field. When present, it must be a valid ResourceRoleType. When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + } + } + }, + "com.github.openshift.api.etcd.v1alpha1.PacemakerSummary": { + "description": "PacemakerSummary provides a high-level summary of cluster state", + "type": "object", + "properties": { + "nodesOnline": { + "description": "nodesOnline is the count of online nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "integer", + "format": "int32" + }, + "nodesTotal": { + "description": "nodesTotal is the total count of configured nodes When present, it must be a valid integer between 0 and 2 inclusive. When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "integer", + "format": "int32" + }, + "pacemakerDaemonState": { + "description": "pacemakerDaemonState indicates the state of the pacemaker daemon PacemakerDaemonStateType can be one of the following values: - Running - the pacemaker daemon is in the 'running' state - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state\n to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc.\nWhen present, it must be a valid PacemakerDaemonStateType. When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "quorumStatus": { + "description": "quorumStatus indicates if the cluster has quorum QuorumStatusType can be one of the following values: - Quorate - the cluster has quorum - NoQuorum - the cluster does not have quorum When present, it must be a valid QuorumStatusType. When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "string" + }, + "resourcesStarted": { + "description": "resourcesStarted is the count of started resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The number could be less than 6 if the cluster is starting up or not healthy. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "integer", + "format": "int32" + }, + "resourcesTotal": { + "description": "resourcesTotal is the total count of configured resources When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output.", + "type": "integer", + "format": "int32" + } + } + }, "com.github.openshift.api.example.v1.CELUnion": { "description": "CELUnion demonstrates how to use a discriminated union and how to validate it using CEL.", "type": "object", diff --git a/payload-manifests/crds/0000_25_etcd_01_pacemakerclusters.crd.yaml b/payload-manifests/crds/0000_25_etcd_01_pacemakerclusters.crd.yaml new file mode 100644 index 00000000000..093d5ff9cdd --- /dev/null +++ b/payload-manifests/crds/0000_25_etcd_01_pacemakerclusters.crd.yaml @@ -0,0 +1,425 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2544 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/ibm-cloud-managed: "true" + include.release.openshift.io/self-managed-high-availability: "true" + name: pacemakerclusters.etcd.openshift.io +spec: + group: etcd.openshift.io + names: + kind: PacemakerCluster + listKind: PacemakerClusterList + plural: pacemakerclusters + singular: pacemakercluster + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: 'Compatibility level 4: No compatibility is provided, the API + can change at any point for any reason. These capabilities should not be + used by applications needing long term support.' + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + spec is an empty spec to satisfy Kubernetes API conventions. + PacemakerCluster is a status-only resource and does not use spec for configuration. + type: object + status: + description: |- + status contains the actual pacemaker cluster status information collected from the cluster. + This follows the Design Principle: Act on Deterministic Information. + When fields are not present, they are treated as unknown and no actions are taken by the cluster-etcd-operator. + properties: + collectionError: + description: |- + collectionError contains any error encountered while collecting status + When present, it must be between 1 and 2048 characters long (max 2KB). This limit ensures that the error message can be displayed in a human-readable format. + When not present, no collection errors are available and the status collection is assumed to be successful. + maxLength: 2048 + minLength: 1 + type: string + fencingHistory: + description: |- + fencingHistory provides recent fencing events + When present, it must be a list of 1 or more PacemakerFencingEvent objects. + When not present, the fencing history is not available. This is the expected status for a healthy cluster. + Fencing history being capped at 16 is a reasonable limit to prevent abuse of the API, since the fencing history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for fencing events that occured within the last 24 hours (unless they have already been reported). + items: + description: PacemakerFencingEvent represents a single fencing event + from fence history + properties: + action: + description: |- + action is the fencing action performed + FencingActionType can be one of the following values: + - reboot - the node was rebooted + - off - the node was turned off + - on - the node was turned on + When present, it must be a valid FencingActionType. + When not present, the fencing action is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - reboot + - false + - true + type: string + completed: + allOf: + - format: date-time + - format: RFC3339 + description: |- + completed is the timestamp when the fencing event was completed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + status: + description: |- + status is the status of the fencing operation + FencingStatusType can be one of the following values: + - success - the fencing event was successful + - failed - the fencing event failed + - pending - the fencing event is pending + When present, it must be a valid FencingStatusType. + When not present, the fencing status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - success + - failed + - pending + type: string + target: + description: |- + target is the node that was fenced + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - completed + - target + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + lastUpdated: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastUpdated is the timestamp when this status was last updated + When present, it must be a valid timestamp in RFC3339 format and cannot be set to an earlier timestamp than the current value + This is the most critical field in the status object because we can use it to warn if the status collection has gone stale. + It is optional upon initial creation, but required once it has been set. + type: string + nodeHistory: + description: |- + nodeHistory provides recent operation history for troubleshooting + When present, it must be a list of 1 or more PacemakerNodeHistoryEntry objects. + When not present, the node history is not available. This is the expected status for a healthy cluster. + Node history being capped at 16 is a reasonable limit to prevent abuse of the API, since the action history reported by the cluster + is presented as a recorded event. + The healthchecker runs every 30 seconds and creates events for failed operations that occured within the last 5 minutes (unless they have already been reported). + items: + description: PacemakerNodeHistoryEntry represents a single operation + history entry from node_history + properties: + lastRCChange: + allOf: + - format: date-time + - format: RFC3339 + description: |- + lastRCChange is the timestamp when the RC last changed + It must be a valid timestamp in RFC3339 format and cannot be empty. + type: string + node: + description: |- + node is the node where the operation occurred + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + operation: + description: |- + operation is the operation that was performed (e.g., "monitor", "start", "stop") + Unlike other fields, this is not an enum because while "monitor", "start" and "stop" + are the most common, resource agents can define their own operations. + It must be a valid string between 1 and 32 characters long and cannot be empty. + maxLength: 32 + minLength: 1 + type: string + rc: + description: |- + rc is the return code from the operation + When present, it must be a valid integer between 0 and 2147483647 (max 32-bit int) inclusive. + When not present, the return code is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + rcText: + description: |- + rcText is the human-readable return code text (e.g., "ok", "error", "not running") + When present, it must be a valid string between 1 and 32 characters long. This is a human-readable string and is not validated against any specific format. + When not present, the return code text is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 32 + minLength: 1 + type: string + resource: + description: |- + resource is the resource that was operated on + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + required: + - lastRCChange + - node + - operation + - resource + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + nodes: + description: |- + nodes provides detailed information about each node in the cluster + When present, it must be a list of 1 or 2 PacemakerNodeStatus objects. Two is expected in a healthy cluster. + When not present, the nodes are not available. This likely indicates that there is an error parsing the raw XML output. + If only one node is present, this indicates that the cluster is in the process of replacing a failed node. + items: + description: NodeStatus represents the status of a single node in + the Pacemaker cluster + properties: + ipAddress: + description: |- + ipAddress is the canonical IPv4 or IPv6 address of the node + When present, it must be a valid canonical global unicast IPv4 or IPv6 address (including private/RFC1918 addresses). + This excludes special addresses like unspecified, loopback, link-local, and multicast. + When not present, the IP address is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 39 + minLength: 2 + type: string + x-kubernetes-validations: + - message: ipAddress must be a valid canonical global unicast + IPv4 or IPv6 address + rule: isIP(self) && ip.isCanonical(self) && ip(self).isGlobalUnicast() + mode: + description: |- + mode indicates if the node is in active or standby mode + NodeModeType can be one of the following values: + - Active - the node is in active mode + - Standby - the node is in standby mode + When present, it must be a valid NodeModeType. + When not present, the node mode is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Standby + type: string + name: + description: |- + name is the name of the node + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + onlineStatus: + description: |- + onlineStatus indicates if the node is online or offline + NodeOnlineStatusType can be one of the following values: + - Online - the node is online + - Offline - the node is offline + When present, it must be a valid NodeOnlineStatusType. + When not present, the online status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Online + - Offline + type: string + required: + - name + type: object + maxItems: 2 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + rawXML: + description: |- + rawXML contains the raw XML output from pcs status xml command. + Kept for debugging purposes only; healthcheck should not need to parse this. + When present, it must be between 1 and 262144 characters long (max 256KB). This limit protects the API from XML bombs and excessive memory consumption. + When not present, the raw XML output is not available. + maxLength: 262144 + minLength: 1 + type: string + resources: + description: |- + resources provides detailed information about each resource in the cluster + When present, it must be a list of 1 or more PacemakerResourceStatus objects. + When not present, the resources are not available. This likely indicates that there is an error parsing the raw XML output. + The number of resources is expected to be between 1 and 16, but is most likely to be exactly 6. + The critical resources that expect to run on both nodes are: kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + This could drift over time as Two Node Fencing matures, so this is left flexible. + items: + description: PacemakerResourceStatus represents the status of a + single resource in the Pacemaker cluster + properties: + activeStatus: + description: |- + activeStatus indicates if the resource is active or inactive + ResourceActiveStatusType can be one of the following values: + - Active - the resource is active + - Inactive - the resource is inactive + When present, it must be a valid ResourceActiveStatusType. + When not present, the active status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Active + - Inactive + type: string + name: + description: |- + name is the name of the resource + It must be a valid string between 1 and 256 characters long and cannot be empty. + maxLength: 256 + minLength: 1 + type: string + node: + description: |- + node is the node where the resource is running + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource is not assigned to a node. This typically indicates a stopped or unscheduled resource. It could also imply an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + resourceAgent: + description: |- + resourceAgent is the resource agent type (e.g., "ocf:heartbeat:IPaddr2", "systemd:kubelet") + When present, it must be a valid string between 1 and 256 characters long. + When not present, the resource agent type is unknown. This likely indicates that there is an error parsing the raw XML output. + maxLength: 256 + minLength: 1 + type: string + role: + description: |- + role is the current role of the resource + ResourceRoleType can be one of the following values: + - Started - the resource is started + - Stopped - the resource is stopped + We don't use promoted and unpromoted, so resources in those roles would omit the role field. + When present, it must be a valid ResourceRoleType. + When not present, the resource role is unknown (or an unsupported type like promoted or unpromoted). This likely indicates that there is an error parsing the raw XML output. + enum: + - Started + - Stopped + type: string + required: + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-type: atomic + summary: + description: |- + summary provides high-level counts and flags for the cluster state + TNF clusters don't use advanced pacemaker features like maintenance mode or pacemaker remote nodes, so these are omitted from the summary. + When present, it must be a valid PacemakerSummary object. + When not present, the summary is not available. This likely indicates that there is an error parsing the raw XML output. + properties: + nodesOnline: + description: |- + nodesOnline is the count of online nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes online count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + nodesTotal: + description: |- + nodesTotal is the total count of configured nodes + When present, it must be a valid integer between 0 and 2 inclusive. + When not present, the nodes total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 2 + minimum: 0 + type: integer + pacemakerDaemonState: + description: |- + pacemakerDaemonState indicates the state of the pacemaker daemon + PacemakerDaemonStateType can be one of the following values: + - Running - the pacemaker daemon is in the 'running' state + - KnownNotRunning - the pacemaker daemon is not in the 'running' state. This is left as a blanket state + to cover states like init, wait_for_ping, starting_daemons, shutting_down, shutdown_complete, etc. + When present, it must be a valid PacemakerDaemonStateType. + When not present, the pacemaker daemon state is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Running + - KnownNotRunning + type: string + quorumStatus: + description: |- + quorumStatus indicates if the cluster has quorum + QuorumStatusType can be one of the following values: + - Quorate - the cluster has quorum + - NoQuorum - the cluster does not have quorum + When present, it must be a valid QuorumStatusType. + When not present, the quorum status is unknown. This likely indicates that there is an error parsing the raw XML output. + enum: + - Quorate + - NoQuorum + type: string + resourcesStarted: + description: |- + resourcesStarted is the count of started resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The number could be less than 6 if the cluster is starting up or not healthy. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources started count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + resourcesTotal: + description: |- + resourcesTotal is the total count of configured resources + When present, it must be a valid integer between 0 and 16 inclusive. For a healthy Two Node Fencing (TNF) cluster, this is expected to be 6. + The expected resources are kubelet, etcd, and a fencing resource (i.e. redfish) for each node. + The total number of resources managed by the cluster could drift over time as Two Node Fencing matures, so this is left flexible. + When not present, the resources total count is unknown. This likely indicates that there is an error parsing the raw XML output. + format: int32 + maximum: 16 + minimum: 0 + type: integer + type: object + type: object + x-kubernetes-validations: + - message: lastUpdated must be a newer timestamp than the current value + rule: '!has(oldSelf.lastUpdated) || (has(self.lastUpdated) && self.lastUpdated + >= oldSelf.lastUpdated)' + type: object + served: true + storage: true + subresources: + status: {}