From 63d3b4e1827a67ec64f34e6ffffac872beb60673 Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Tue, 26 May 2026 11:06:33 -0400 Subject: [PATCH 1/4] AGENT-1493: Promote NoRegistryClusterInstall Feature to Default Feature NoRegistryClusterInstall is ready to be supported during Agent installs by default. --- features.md | 2 +- features/features.go | 2 +- ...ternalreleaseimages-SelfManagedHA.crd.yaml | 1 - ...neconfignodes-Hypershift-Default.crd.yaml} | 1 - ...achineconfignodes-Hypershift-OKD.crd.yaml} | 1 - ...confignodes-SelfManagedHA-Default.crd.yaml | 520 ++++++++++++++++++ ...hineconfignodes-SelfManagedHA-OKD.crd.yaml | 520 ++++++++++++++++++ ...ternalreleaseimages-SelfManagedHA.crd.yaml | 1 - ...ternalreleaseimages-SelfManagedHA.crd.yaml | 1 - ...neconfignodes-Hypershift-Default.crd.yaml} | 1 - ...achineconfignodes-Hypershift-OKD.crd.yaml} | 1 - ...confignodes-SelfManagedHA-Default.crd.yaml | 520 ++++++++++++++++++ ...hineconfignodes-SelfManagedHA-OKD.crd.yaml | 520 ++++++++++++++++++ ...eatureGate-4-10-SelfManagedHA-Default.yaml | 6 +- .../featureGate-4-10-SelfManagedHA-OKD.yaml | 6 +- 15 files changed, 2088 insertions(+), 15 deletions(-) rename machineconfiguration/v1/zz_generated.crd-manifests/{0000_80_machine-config_01_machineconfignodes-Default.crd.yaml => 0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml} (99%) rename machineconfiguration/v1/zz_generated.crd-manifests/{0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml => 0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml} (99%) create mode 100644 machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml create mode 100644 machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml rename payload-manifests/crds/{0000_80_machine-config_01_machineconfignodes-Default.crd.yaml => 0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml} (99%) rename payload-manifests/crds/{0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml => 0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml} (99%) create mode 100644 payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml create mode 100644 payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml diff --git a/features.md b/features.md index c78d402696a..42058b1154d 100644 --- a/features.md +++ b/features.md @@ -25,7 +25,6 @@ | NewOLMConfigAPI| | | | Enabled | | | | Enabled | | NewOLMOwnSingleNamespace| | | | Enabled | | | | Enabled | | NewOLMPreflightPermissionChecks| | | | Enabled | | | | Enabled | -| NoRegistryClusterInstall| | | | Enabled | | | | Enabled | | OLMLifecycleAndCompatibility| | | | Enabled | | | | Enabled | | ProvisioningRequestAvailable| | | Enabled | Enabled | | | | | | AWSClusterHostedDNS| | | Enabled | Enabled | | | Enabled | Enabled | @@ -83,6 +82,7 @@ | NewOLM| | Enabled | | Enabled | | Enabled | | Enabled | | NewOLMWebhookProviderOpenshiftServiceCA| | Enabled | | Enabled | | Enabled | | Enabled | | NoOverlayMode| | | Enabled | Enabled | | | Enabled | Enabled | +| NoRegistryClusterInstall| | Enabled | | Enabled | | Enabled | | Enabled | | NutanixMultiSubnets| | | Enabled | Enabled | | | Enabled | Enabled | | OVNObservability| | | Enabled | Enabled | | | Enabled | Enabled | | OnPremDNSRecords| | | Enabled | Enabled | | | Enabled | Enabled | diff --git a/features/features.go b/features/features.go index b45bef77056..66ac73ec871 100644 --- a/features/features.go +++ b/features/features.go @@ -838,7 +838,7 @@ var ( contactPerson("andfasano"). productScope(ocpSpecific). enhancementPR("https://github.com/openshift/enhancements/pull/1821"). - enable(inClusterProfile(SelfManaged), inTechPreviewNoUpgrade(), inDevPreviewNoUpgrade()). + enable(inDefault(), inOKD(), inClusterProfile(SelfManaged), inTechPreviewNoUpgrade(), inDevPreviewNoUpgrade()). mustRegister() FeatureGateAWSClusterHostedDNSInstall = newFeatureGate("AWSClusterHostedDNSInstall"). diff --git a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml index b5b2f6f185c..b96953d1335 100644 --- a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml +++ b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2510 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/self-managed-high-availability: "true" - release.openshift.io/feature-set: CustomNoUpgrade,DevPreviewNoUpgrade,TechPreviewNoUpgrade labels: openshift.io/operator-managed: "" name: internalreleaseimages.machineconfiguration.openshift.io diff --git a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml similarity index 99% rename from machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml rename to machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml index 85c77750465..69920b355ad 100644 --- a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml +++ b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2255 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/ibm-cloud-managed: "true" - include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: Default labels: openshift.io/operator-managed: "" diff --git a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml similarity index 99% rename from machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml rename to machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml index 8263d178336..966080e5bbf 100644 --- a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml +++ b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2255 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/ibm-cloud-managed: "true" - include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: OKD labels: openshift.io/operator-managed: "" diff --git a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml new file mode 100644 index 00000000000..45b3c659850 --- /dev/null +++ b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml @@ -0,0 +1,520 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2255 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/self-managed-high-availability: "true" + release.openshift.io/feature-set: Default + labels: + openshift.io/operator-managed: "" + name: machineconfignodes.machineconfiguration.openshift.io +spec: + group: machineconfiguration.openshift.io + names: + kind: MachineConfigNode + listKind: MachineConfigNodeList + plural: machineconfignodes + singular: machineconfignode + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.pool.name + name: PoolName + type: string + - jsonPath: .spec.configVersion.desired + name: DesiredConfig + type: string + - jsonPath: .status.configVersion.current + name: CurrentConfig + type: string + - jsonPath: .status.conditions[?(@.type=="Updated")].status + name: Updated + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="UpdatePrepared")].status + name: UpdatePrepared + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateExecuted")].status + name: UpdateExecuted + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdatePostActionComplete")].status + name: UpdatePostActionComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateComplete")].status + name: UpdateComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Resumed")].status + name: Resumed + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="AppliedFilesAndOS")].status + name: UpdatedFilesAndOS + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Cordoned")].status + name: CordonedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Drained")].status + name: DrainedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="RebootedNode")].status + name: RebootedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Uncordoned")].status + name: UncordonedNode + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + MachineConfigNode describes the health of the Machines on the system + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). + 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 describes the configuration of the machine config node. + properties: + configVersion: + description: |- + configVersion holds the desired config version for the node targeted by this machine config node resource. + The desired version represents the machine config the node will attempt to update to and gets set before the machine config operator validates + the new machine config against the current machine config. + properties: + desired: + description: |- + desired is the name of the machine config that the the node should be upgraded to. + This value is set when the machine config pool generates a new version of its rendered configuration. + When this value is changed, the machine config daemon starts the node upgrade process. + This value gets set in the machine config node spec once the machine config has been targeted for upgrade and before it is validated. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + node: + description: node contains a reference to the node for this machine + config node. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + pool: + description: |- + pool contains a reference to the machine config pool that this machine config node's + referenced node belongs to. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + required: + - configVersion + - node + - pool + type: object + status: + description: status describes the last observed state of this machine + config node. + properties: + conditions: + description: |- + conditions represent the observations of a machine config node's current state. Valid types are: + UpdatePrepared, UpdateExecuted, UpdatePostActionComplete, UpdateComplete, Updated, Resumed, + Drained, AppliedFilesAndOS, Cordoned, Uncordoned, RebootedNode, NodeDegraded, PinnedImageSetsProgressing, + and PinnedImageSetsDegraded. + The following types are only available when the ImageModeStatusReporting feature gate is enabled: ImagePulledFromRegistry, + AppliedOSImage, AppliedFiles + The following types are only available when the NoRegistryClusterInstall feature gate is enabled: InternalReleaseImageDegraded + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configVersion: + description: configVersion describes the current and desired machine + config version for this node. + properties: + current: + description: |- + current is the name of the machine config currently in use on the node. + This value is updated once the machine config daemon has completed the update of the configuration for the node. + This value should match the desired version unless an upgrade is in progress. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + desired: + description: |- + desired is the MachineConfig the node wants to upgrade to. + This value gets set in the machine config node status once the machine config has been validated + against the current machine config. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + internalReleaseImage: + description: |- + internalReleaseImage describes the status of the release payloads stored in the node. + When specified, an internalReleaseImage custom resource exists on the cluster, and the specified images will be made available on the control plane nodes. + This field will reflect the actual on-disk state of those release images. + properties: + releases: + description: |- + releases is a list of the release bundles currently owned and managed by the + cluster. + A release bundle content could be safely pulled only when its Conditions field + contains at least an Available entry set to "True" and Degraded to "False". + Entries must be unique, keyed on the name field. + releases must contain at least one entry and must not exceed 32 entries. + items: + description: |- + MachineConfigNodeStatusInternalReleaseImageRef is used to provide a more detailed reference for + a release bundle. + properties: + conditions: + description: |- + conditions represent the observations of an internal release image current state. Valid types are: + Mounted, Installing, Available, Removing and Degraded. + + If Mounted is true, that means that a valid ISO has been mounted on the current node. + If Installing is true, that means that a new release bundle is currently being copied on the current node, and not yet completed. + If Available is true, it means that the release has been previously installed on the current node, and it can be used. + If Removing is true, it means that a release deletion is in progress on the current node, and not yet completed. + If Degraded is true, that means something has gone wrong in the current node. + items: + description: Condition contains details for one aspect + of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, + False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in + foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 5 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + image: + description: |- + image is an OCP release image referenced by digest. + The format of the image pull spec is: host[:port][/namespace]/name@sha256:, + where the digest must be 64 characters long, and consist only of lowercase hexadecimal characters, a-f and 0-9. + The host must be either exactly "localhost" or a dot-qualified domain name. + Single-label hosts other than "localhost" are not permitted. + The length of the whole spec must be between 1 to 447 characters. + The field is optional, and it will be provided after a release will be successfully installed. + maxLength: 447 + minLength: 1 + type: string + x-kubernetes-validations: + - message: the OCI Image reference must end with a valid + '@sha256:' suffix, where '' is 64 characters + long + rule: (self.split('@').size() == 2 && self.split('@')[1].matches('^sha256:[a-f0-9]{64}$')) + - message: the OCI Image name should follow the host[:port][/namespace]/name + format, resembling a valid URL without the scheme; host + must be either 'localhost' or a dot-qualified domain + name + rule: (self.split('@')[0].matches('^(localhost|([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+)(:[0-9]{2,5})?/([a-zA-Z0-9-_]{0,61}/)?[a-zA-Z0-9-_.]*?$')) + name: + description: |- + name indicates the desired release bundle identifier. This field is required and must be between 1 and 64 characters long. + The expected name format is ocp-release-bundle--. + maxLength: 64 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must be ocp-release-bundle-- + and <= 64 chars + rule: self.matches('^ocp-release-bundle-[0-9]+\\.[0-9]+\\.[0-9]+-[A-Za-z0-9._-]+$') + required: + - name + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - releases + type: object + observedGeneration: + description: |- + observedGeneration represents the generation of the MachineConfigNode object observed by the Machine Config Operator's controller. + This field is updated when the controller observes a change to the desiredConfig in the configVersion of the machine config node spec. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedGeneration must not decrease + rule: self >= oldSelf + pinnedImageSets: + description: pinnedImageSets describes the current and desired pinned + image sets for this node. + items: + description: MachineConfigNodeStatusPinnedImageSet holds information + about the current, desired, and failed pinned image sets for the + observed machine config node. + properties: + currentGeneration: + description: currentGeneration is the generation of the pinned + image set that has most recently been successfully pulled + and pinned on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: currentGeneration must not decrease + rule: self >= oldSelf + desiredGeneration: + description: desiredGeneration is the generation of the pinned + image set that is targeted to be pulled and pinned on this + node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: desiredGeneration must not decrease + rule: self >= oldSelf + lastFailedGeneration: + description: lastFailedGeneration is the generation of the most + recent pinned image set that failed to be pulled and pinned + on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: lastFailedGeneration must not decrease + rule: self >= oldSelf + lastFailedGenerationError: + description: |- + lastFailedGenerationError is the error explaining why the desired images failed to be pulled and pinned. + The error is an empty string if the image pull and pin is successful. + maxLength: 32768 + type: string + name: + description: |- + name is the name of the pinned image set. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start + and end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + x-kubernetes-validations: + - message: desired generation must be greater than or equal to the + current generation + rule: 'has(self.desiredGeneration) && has(self.currentGeneration) + ? self.desiredGeneration >= self.currentGeneration : true' + - message: desired generation must be greater than or equal to the + last failed generation + rule: 'has(self.lastFailedGeneration) && has(self.desiredGeneration) + ? self.desiredGeneration >= self.lastFailedGeneration : true' + - message: last failed generation error must be defined on image + pull and pin failure + rule: 'has(self.lastFailedGeneration) ? has(self.lastFailedGenerationError) + : true' + maxItems: 100 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: spec.node.name should match metadata.name + rule: self.metadata.name == self.spec.node.name + served: true + storage: true + subresources: + status: {} diff --git a/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml new file mode 100644 index 00000000000..fa7edbcf9aa --- /dev/null +++ b/machineconfiguration/v1/zz_generated.crd-manifests/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml @@ -0,0 +1,520 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2255 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/self-managed-high-availability: "true" + release.openshift.io/feature-set: OKD + labels: + openshift.io/operator-managed: "" + name: machineconfignodes.machineconfiguration.openshift.io +spec: + group: machineconfiguration.openshift.io + names: + kind: MachineConfigNode + listKind: MachineConfigNodeList + plural: machineconfignodes + singular: machineconfignode + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.pool.name + name: PoolName + type: string + - jsonPath: .spec.configVersion.desired + name: DesiredConfig + type: string + - jsonPath: .status.configVersion.current + name: CurrentConfig + type: string + - jsonPath: .status.conditions[?(@.type=="Updated")].status + name: Updated + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="UpdatePrepared")].status + name: UpdatePrepared + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateExecuted")].status + name: UpdateExecuted + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdatePostActionComplete")].status + name: UpdatePostActionComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateComplete")].status + name: UpdateComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Resumed")].status + name: Resumed + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="AppliedFilesAndOS")].status + name: UpdatedFilesAndOS + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Cordoned")].status + name: CordonedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Drained")].status + name: DrainedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="RebootedNode")].status + name: RebootedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Uncordoned")].status + name: UncordonedNode + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + MachineConfigNode describes the health of the Machines on the system + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). + 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 describes the configuration of the machine config node. + properties: + configVersion: + description: |- + configVersion holds the desired config version for the node targeted by this machine config node resource. + The desired version represents the machine config the node will attempt to update to and gets set before the machine config operator validates + the new machine config against the current machine config. + properties: + desired: + description: |- + desired is the name of the machine config that the the node should be upgraded to. + This value is set when the machine config pool generates a new version of its rendered configuration. + When this value is changed, the machine config daemon starts the node upgrade process. + This value gets set in the machine config node spec once the machine config has been targeted for upgrade and before it is validated. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + node: + description: node contains a reference to the node for this machine + config node. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + pool: + description: |- + pool contains a reference to the machine config pool that this machine config node's + referenced node belongs to. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + required: + - configVersion + - node + - pool + type: object + status: + description: status describes the last observed state of this machine + config node. + properties: + conditions: + description: |- + conditions represent the observations of a machine config node's current state. Valid types are: + UpdatePrepared, UpdateExecuted, UpdatePostActionComplete, UpdateComplete, Updated, Resumed, + Drained, AppliedFilesAndOS, Cordoned, Uncordoned, RebootedNode, NodeDegraded, PinnedImageSetsProgressing, + and PinnedImageSetsDegraded. + The following types are only available when the ImageModeStatusReporting feature gate is enabled: ImagePulledFromRegistry, + AppliedOSImage, AppliedFiles + The following types are only available when the NoRegistryClusterInstall feature gate is enabled: InternalReleaseImageDegraded + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configVersion: + description: configVersion describes the current and desired machine + config version for this node. + properties: + current: + description: |- + current is the name of the machine config currently in use on the node. + This value is updated once the machine config daemon has completed the update of the configuration for the node. + This value should match the desired version unless an upgrade is in progress. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + desired: + description: |- + desired is the MachineConfig the node wants to upgrade to. + This value gets set in the machine config node status once the machine config has been validated + against the current machine config. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + internalReleaseImage: + description: |- + internalReleaseImage describes the status of the release payloads stored in the node. + When specified, an internalReleaseImage custom resource exists on the cluster, and the specified images will be made available on the control plane nodes. + This field will reflect the actual on-disk state of those release images. + properties: + releases: + description: |- + releases is a list of the release bundles currently owned and managed by the + cluster. + A release bundle content could be safely pulled only when its Conditions field + contains at least an Available entry set to "True" and Degraded to "False". + Entries must be unique, keyed on the name field. + releases must contain at least one entry and must not exceed 32 entries. + items: + description: |- + MachineConfigNodeStatusInternalReleaseImageRef is used to provide a more detailed reference for + a release bundle. + properties: + conditions: + description: |- + conditions represent the observations of an internal release image current state. Valid types are: + Mounted, Installing, Available, Removing and Degraded. + + If Mounted is true, that means that a valid ISO has been mounted on the current node. + If Installing is true, that means that a new release bundle is currently being copied on the current node, and not yet completed. + If Available is true, it means that the release has been previously installed on the current node, and it can be used. + If Removing is true, it means that a release deletion is in progress on the current node, and not yet completed. + If Degraded is true, that means something has gone wrong in the current node. + items: + description: Condition contains details for one aspect + of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, + False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in + foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 5 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + image: + description: |- + image is an OCP release image referenced by digest. + The format of the image pull spec is: host[:port][/namespace]/name@sha256:, + where the digest must be 64 characters long, and consist only of lowercase hexadecimal characters, a-f and 0-9. + The host must be either exactly "localhost" or a dot-qualified domain name. + Single-label hosts other than "localhost" are not permitted. + The length of the whole spec must be between 1 to 447 characters. + The field is optional, and it will be provided after a release will be successfully installed. + maxLength: 447 + minLength: 1 + type: string + x-kubernetes-validations: + - message: the OCI Image reference must end with a valid + '@sha256:' suffix, where '' is 64 characters + long + rule: (self.split('@').size() == 2 && self.split('@')[1].matches('^sha256:[a-f0-9]{64}$')) + - message: the OCI Image name should follow the host[:port][/namespace]/name + format, resembling a valid URL without the scheme; host + must be either 'localhost' or a dot-qualified domain + name + rule: (self.split('@')[0].matches('^(localhost|([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+)(:[0-9]{2,5})?/([a-zA-Z0-9-_]{0,61}/)?[a-zA-Z0-9-_.]*?$')) + name: + description: |- + name indicates the desired release bundle identifier. This field is required and must be between 1 and 64 characters long. + The expected name format is ocp-release-bundle--. + maxLength: 64 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must be ocp-release-bundle-- + and <= 64 chars + rule: self.matches('^ocp-release-bundle-[0-9]+\\.[0-9]+\\.[0-9]+-[A-Za-z0-9._-]+$') + required: + - name + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - releases + type: object + observedGeneration: + description: |- + observedGeneration represents the generation of the MachineConfigNode object observed by the Machine Config Operator's controller. + This field is updated when the controller observes a change to the desiredConfig in the configVersion of the machine config node spec. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedGeneration must not decrease + rule: self >= oldSelf + pinnedImageSets: + description: pinnedImageSets describes the current and desired pinned + image sets for this node. + items: + description: MachineConfigNodeStatusPinnedImageSet holds information + about the current, desired, and failed pinned image sets for the + observed machine config node. + properties: + currentGeneration: + description: currentGeneration is the generation of the pinned + image set that has most recently been successfully pulled + and pinned on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: currentGeneration must not decrease + rule: self >= oldSelf + desiredGeneration: + description: desiredGeneration is the generation of the pinned + image set that is targeted to be pulled and pinned on this + node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: desiredGeneration must not decrease + rule: self >= oldSelf + lastFailedGeneration: + description: lastFailedGeneration is the generation of the most + recent pinned image set that failed to be pulled and pinned + on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: lastFailedGeneration must not decrease + rule: self >= oldSelf + lastFailedGenerationError: + description: |- + lastFailedGenerationError is the error explaining why the desired images failed to be pulled and pinned. + The error is an empty string if the image pull and pin is successful. + maxLength: 32768 + type: string + name: + description: |- + name is the name of the pinned image set. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start + and end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + x-kubernetes-validations: + - message: desired generation must be greater than or equal to the + current generation + rule: 'has(self.desiredGeneration) && has(self.currentGeneration) + ? self.desiredGeneration >= self.currentGeneration : true' + - message: desired generation must be greater than or equal to the + last failed generation + rule: 'has(self.lastFailedGeneration) && has(self.desiredGeneration) + ? self.desiredGeneration >= self.lastFailedGeneration : true' + - message: last failed generation error must be defined on image + pull and pin failure + rule: 'has(self.lastFailedGeneration) ? has(self.lastFailedGenerationError) + : true' + maxItems: 100 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: spec.node.name should match metadata.name + rule: self.metadata.name == self.spec.node.name + served: true + storage: true + subresources: + status: {} diff --git a/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml b/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml index c28df43cb11..e408ecb163a 100644 --- a/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml +++ b/machineconfiguration/v1alpha1/zz_generated.crd-manifests/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2510 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/self-managed-high-availability: "true" - release.openshift.io/feature-set: CustomNoUpgrade,DevPreviewNoUpgrade,TechPreviewNoUpgrade labels: openshift.io/operator-managed: "" name: internalreleaseimages.machineconfiguration.openshift.io diff --git a/payload-manifests/crds/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml index b5b2f6f185c..b96953d1335 100644 --- a/payload-manifests/crds/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml +++ b/payload-manifests/crds/0000_80_machine-config_01_internalreleaseimages-SelfManagedHA.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2510 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/self-managed-high-availability: "true" - release.openshift.io/feature-set: CustomNoUpgrade,DevPreviewNoUpgrade,TechPreviewNoUpgrade labels: openshift.io/operator-managed: "" name: internalreleaseimages.machineconfiguration.openshift.io diff --git a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml similarity index 99% rename from payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml rename to payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml index 85c77750465..69920b355ad 100644 --- a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Default.crd.yaml +++ b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-Default.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2255 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/ibm-cloud-managed: "true" - include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: Default labels: openshift.io/operator-managed: "" diff --git a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml similarity index 99% rename from payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml rename to payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml index 8263d178336..966080e5bbf 100644 --- a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-OKD.crd.yaml +++ b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-Hypershift-OKD.crd.yaml @@ -5,7 +5,6 @@ metadata: api-approved.openshift.io: https://github.com/openshift/api/pull/2255 api.openshift.io/merged-by-featuregates: "true" include.release.openshift.io/ibm-cloud-managed: "true" - include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: OKD labels: openshift.io/operator-managed: "" diff --git a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml new file mode 100644 index 00000000000..45b3c659850 --- /dev/null +++ b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-Default.crd.yaml @@ -0,0 +1,520 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2255 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/self-managed-high-availability: "true" + release.openshift.io/feature-set: Default + labels: + openshift.io/operator-managed: "" + name: machineconfignodes.machineconfiguration.openshift.io +spec: + group: machineconfiguration.openshift.io + names: + kind: MachineConfigNode + listKind: MachineConfigNodeList + plural: machineconfignodes + singular: machineconfignode + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.pool.name + name: PoolName + type: string + - jsonPath: .spec.configVersion.desired + name: DesiredConfig + type: string + - jsonPath: .status.configVersion.current + name: CurrentConfig + type: string + - jsonPath: .status.conditions[?(@.type=="Updated")].status + name: Updated + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="UpdatePrepared")].status + name: UpdatePrepared + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateExecuted")].status + name: UpdateExecuted + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdatePostActionComplete")].status + name: UpdatePostActionComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateComplete")].status + name: UpdateComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Resumed")].status + name: Resumed + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="AppliedFilesAndOS")].status + name: UpdatedFilesAndOS + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Cordoned")].status + name: CordonedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Drained")].status + name: DrainedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="RebootedNode")].status + name: RebootedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Uncordoned")].status + name: UncordonedNode + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + MachineConfigNode describes the health of the Machines on the system + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). + 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 describes the configuration of the machine config node. + properties: + configVersion: + description: |- + configVersion holds the desired config version for the node targeted by this machine config node resource. + The desired version represents the machine config the node will attempt to update to and gets set before the machine config operator validates + the new machine config against the current machine config. + properties: + desired: + description: |- + desired is the name of the machine config that the the node should be upgraded to. + This value is set when the machine config pool generates a new version of its rendered configuration. + When this value is changed, the machine config daemon starts the node upgrade process. + This value gets set in the machine config node spec once the machine config has been targeted for upgrade and before it is validated. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + node: + description: node contains a reference to the node for this machine + config node. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + pool: + description: |- + pool contains a reference to the machine config pool that this machine config node's + referenced node belongs to. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + required: + - configVersion + - node + - pool + type: object + status: + description: status describes the last observed state of this machine + config node. + properties: + conditions: + description: |- + conditions represent the observations of a machine config node's current state. Valid types are: + UpdatePrepared, UpdateExecuted, UpdatePostActionComplete, UpdateComplete, Updated, Resumed, + Drained, AppliedFilesAndOS, Cordoned, Uncordoned, RebootedNode, NodeDegraded, PinnedImageSetsProgressing, + and PinnedImageSetsDegraded. + The following types are only available when the ImageModeStatusReporting feature gate is enabled: ImagePulledFromRegistry, + AppliedOSImage, AppliedFiles + The following types are only available when the NoRegistryClusterInstall feature gate is enabled: InternalReleaseImageDegraded + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configVersion: + description: configVersion describes the current and desired machine + config version for this node. + properties: + current: + description: |- + current is the name of the machine config currently in use on the node. + This value is updated once the machine config daemon has completed the update of the configuration for the node. + This value should match the desired version unless an upgrade is in progress. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + desired: + description: |- + desired is the MachineConfig the node wants to upgrade to. + This value gets set in the machine config node status once the machine config has been validated + against the current machine config. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + internalReleaseImage: + description: |- + internalReleaseImage describes the status of the release payloads stored in the node. + When specified, an internalReleaseImage custom resource exists on the cluster, and the specified images will be made available on the control plane nodes. + This field will reflect the actual on-disk state of those release images. + properties: + releases: + description: |- + releases is a list of the release bundles currently owned and managed by the + cluster. + A release bundle content could be safely pulled only when its Conditions field + contains at least an Available entry set to "True" and Degraded to "False". + Entries must be unique, keyed on the name field. + releases must contain at least one entry and must not exceed 32 entries. + items: + description: |- + MachineConfigNodeStatusInternalReleaseImageRef is used to provide a more detailed reference for + a release bundle. + properties: + conditions: + description: |- + conditions represent the observations of an internal release image current state. Valid types are: + Mounted, Installing, Available, Removing and Degraded. + + If Mounted is true, that means that a valid ISO has been mounted on the current node. + If Installing is true, that means that a new release bundle is currently being copied on the current node, and not yet completed. + If Available is true, it means that the release has been previously installed on the current node, and it can be used. + If Removing is true, it means that a release deletion is in progress on the current node, and not yet completed. + If Degraded is true, that means something has gone wrong in the current node. + items: + description: Condition contains details for one aspect + of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, + False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in + foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 5 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + image: + description: |- + image is an OCP release image referenced by digest. + The format of the image pull spec is: host[:port][/namespace]/name@sha256:, + where the digest must be 64 characters long, and consist only of lowercase hexadecimal characters, a-f and 0-9. + The host must be either exactly "localhost" or a dot-qualified domain name. + Single-label hosts other than "localhost" are not permitted. + The length of the whole spec must be between 1 to 447 characters. + The field is optional, and it will be provided after a release will be successfully installed. + maxLength: 447 + minLength: 1 + type: string + x-kubernetes-validations: + - message: the OCI Image reference must end with a valid + '@sha256:' suffix, where '' is 64 characters + long + rule: (self.split('@').size() == 2 && self.split('@')[1].matches('^sha256:[a-f0-9]{64}$')) + - message: the OCI Image name should follow the host[:port][/namespace]/name + format, resembling a valid URL without the scheme; host + must be either 'localhost' or a dot-qualified domain + name + rule: (self.split('@')[0].matches('^(localhost|([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+)(:[0-9]{2,5})?/([a-zA-Z0-9-_]{0,61}/)?[a-zA-Z0-9-_.]*?$')) + name: + description: |- + name indicates the desired release bundle identifier. This field is required and must be between 1 and 64 characters long. + The expected name format is ocp-release-bundle--. + maxLength: 64 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must be ocp-release-bundle-- + and <= 64 chars + rule: self.matches('^ocp-release-bundle-[0-9]+\\.[0-9]+\\.[0-9]+-[A-Za-z0-9._-]+$') + required: + - name + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - releases + type: object + observedGeneration: + description: |- + observedGeneration represents the generation of the MachineConfigNode object observed by the Machine Config Operator's controller. + This field is updated when the controller observes a change to the desiredConfig in the configVersion of the machine config node spec. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedGeneration must not decrease + rule: self >= oldSelf + pinnedImageSets: + description: pinnedImageSets describes the current and desired pinned + image sets for this node. + items: + description: MachineConfigNodeStatusPinnedImageSet holds information + about the current, desired, and failed pinned image sets for the + observed machine config node. + properties: + currentGeneration: + description: currentGeneration is the generation of the pinned + image set that has most recently been successfully pulled + and pinned on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: currentGeneration must not decrease + rule: self >= oldSelf + desiredGeneration: + description: desiredGeneration is the generation of the pinned + image set that is targeted to be pulled and pinned on this + node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: desiredGeneration must not decrease + rule: self >= oldSelf + lastFailedGeneration: + description: lastFailedGeneration is the generation of the most + recent pinned image set that failed to be pulled and pinned + on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: lastFailedGeneration must not decrease + rule: self >= oldSelf + lastFailedGenerationError: + description: |- + lastFailedGenerationError is the error explaining why the desired images failed to be pulled and pinned. + The error is an empty string if the image pull and pin is successful. + maxLength: 32768 + type: string + name: + description: |- + name is the name of the pinned image set. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start + and end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + x-kubernetes-validations: + - message: desired generation must be greater than or equal to the + current generation + rule: 'has(self.desiredGeneration) && has(self.currentGeneration) + ? self.desiredGeneration >= self.currentGeneration : true' + - message: desired generation must be greater than or equal to the + last failed generation + rule: 'has(self.lastFailedGeneration) && has(self.desiredGeneration) + ? self.desiredGeneration >= self.lastFailedGeneration : true' + - message: last failed generation error must be defined on image + pull and pin failure + rule: 'has(self.lastFailedGeneration) ? has(self.lastFailedGenerationError) + : true' + maxItems: 100 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: spec.node.name should match metadata.name + rule: self.metadata.name == self.spec.node.name + served: true + storage: true + subresources: + status: {} diff --git a/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml new file mode 100644 index 00000000000..fa7edbcf9aa --- /dev/null +++ b/payload-manifests/crds/0000_80_machine-config_01_machineconfignodes-SelfManagedHA-OKD.crd.yaml @@ -0,0 +1,520 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.openshift.io: https://github.com/openshift/api/pull/2255 + api.openshift.io/merged-by-featuregates: "true" + include.release.openshift.io/self-managed-high-availability: "true" + release.openshift.io/feature-set: OKD + labels: + openshift.io/operator-managed: "" + name: machineconfignodes.machineconfiguration.openshift.io +spec: + group: machineconfiguration.openshift.io + names: + kind: MachineConfigNode + listKind: MachineConfigNodeList + plural: machineconfignodes + singular: machineconfignode + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.pool.name + name: PoolName + type: string + - jsonPath: .spec.configVersion.desired + name: DesiredConfig + type: string + - jsonPath: .status.configVersion.current + name: CurrentConfig + type: string + - jsonPath: .status.conditions[?(@.type=="Updated")].status + name: Updated + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.conditions[?(@.type=="UpdatePrepared")].status + name: UpdatePrepared + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateExecuted")].status + name: UpdateExecuted + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdatePostActionComplete")].status + name: UpdatePostActionComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="UpdateComplete")].status + name: UpdateComplete + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Resumed")].status + name: Resumed + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="AppliedFilesAndOS")].status + name: UpdatedFilesAndOS + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Cordoned")].status + name: CordonedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Drained")].status + name: DrainedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="RebootedNode")].status + name: RebootedNode + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=="Uncordoned")].status + name: UncordonedNode + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + MachineConfigNode describes the health of the Machines on the system + Compatibility level 1: Stable within a major release for a minimum of 12 months or 3 minor releases (whichever is longer). + 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 describes the configuration of the machine config node. + properties: + configVersion: + description: |- + configVersion holds the desired config version for the node targeted by this machine config node resource. + The desired version represents the machine config the node will attempt to update to and gets set before the machine config operator validates + the new machine config against the current machine config. + properties: + desired: + description: |- + desired is the name of the machine config that the the node should be upgraded to. + This value is set when the machine config pool generates a new version of its rendered configuration. + When this value is changed, the machine config daemon starts the node upgrade process. + This value gets set in the machine config node spec once the machine config has been targeted for upgrade and before it is validated. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + node: + description: node contains a reference to the node for this machine + config node. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + pool: + description: |- + pool contains a reference to the machine config pool that this machine config node's + referenced node belongs to. + properties: + name: + description: |- + name is the name of the object being referenced. For example, this can represent a machine + config pool or node name. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + required: + - configVersion + - node + - pool + type: object + status: + description: status describes the last observed state of this machine + config node. + properties: + conditions: + description: |- + conditions represent the observations of a machine config node's current state. Valid types are: + UpdatePrepared, UpdateExecuted, UpdatePostActionComplete, UpdateComplete, Updated, Resumed, + Drained, AppliedFilesAndOS, Cordoned, Uncordoned, RebootedNode, NodeDegraded, PinnedImageSetsProgressing, + and PinnedImageSetsDegraded. + The following types are only available when the ImageModeStatusReporting feature gate is enabled: ImagePulledFromRegistry, + AppliedOSImage, AppliedFiles + The following types are only available when the NoRegistryClusterInstall feature gate is enabled: InternalReleaseImageDegraded + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + configVersion: + description: configVersion describes the current and desired machine + config version for this node. + properties: + current: + description: |- + current is the name of the machine config currently in use on the node. + This value is updated once the machine config daemon has completed the update of the configuration for the node. + This value should match the desired version unless an upgrade is in progress. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + desired: + description: |- + desired is the MachineConfig the node wants to upgrade to. + This value gets set in the machine config node status once the machine config has been validated + against the current machine config. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start and + end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - desired + type: object + internalReleaseImage: + description: |- + internalReleaseImage describes the status of the release payloads stored in the node. + When specified, an internalReleaseImage custom resource exists on the cluster, and the specified images will be made available on the control plane nodes. + This field will reflect the actual on-disk state of those release images. + properties: + releases: + description: |- + releases is a list of the release bundles currently owned and managed by the + cluster. + A release bundle content could be safely pulled only when its Conditions field + contains at least an Available entry set to "True" and Degraded to "False". + Entries must be unique, keyed on the name field. + releases must contain at least one entry and must not exceed 32 entries. + items: + description: |- + MachineConfigNodeStatusInternalReleaseImageRef is used to provide a more detailed reference for + a release bundle. + properties: + conditions: + description: |- + conditions represent the observations of an internal release image current state. Valid types are: + Mounted, Installing, Available, Removing and Degraded. + + If Mounted is true, that means that a valid ISO has been mounted on the current node. + If Installing is true, that means that a new release bundle is currently being copied on the current node, and not yet completed. + If Available is true, it means that the release has been previously installed on the current node, and it can be used. + If Removing is true, it means that a release deletion is in progress on the current node, and not yet completed. + If Degraded is true, that means something has gone wrong in the current node. + items: + description: Condition contains details for one aspect + of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, + False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in + foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 5 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + image: + description: |- + image is an OCP release image referenced by digest. + The format of the image pull spec is: host[:port][/namespace]/name@sha256:, + where the digest must be 64 characters long, and consist only of lowercase hexadecimal characters, a-f and 0-9. + The host must be either exactly "localhost" or a dot-qualified domain name. + Single-label hosts other than "localhost" are not permitted. + The length of the whole spec must be between 1 to 447 characters. + The field is optional, and it will be provided after a release will be successfully installed. + maxLength: 447 + minLength: 1 + type: string + x-kubernetes-validations: + - message: the OCI Image reference must end with a valid + '@sha256:' suffix, where '' is 64 characters + long + rule: (self.split('@').size() == 2 && self.split('@')[1].matches('^sha256:[a-f0-9]{64}$')) + - message: the OCI Image name should follow the host[:port][/namespace]/name + format, resembling a valid URL without the scheme; host + must be either 'localhost' or a dot-qualified domain + name + rule: (self.split('@')[0].matches('^(localhost|([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+)(:[0-9]{2,5})?/([a-zA-Z0-9-_]{0,61}/)?[a-zA-Z0-9-_.]*?$')) + name: + description: |- + name indicates the desired release bundle identifier. This field is required and must be between 1 and 64 characters long. + The expected name format is ocp-release-bundle--. + maxLength: 64 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must be ocp-release-bundle-- + and <= 64 chars + rule: self.matches('^ocp-release-bundle-[0-9]+\\.[0-9]+\\.[0-9]+-[A-Za-z0-9._-]+$') + required: + - name + type: object + maxItems: 32 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - releases + type: object + observedGeneration: + description: |- + observedGeneration represents the generation of the MachineConfigNode object observed by the Machine Config Operator's controller. + This field is updated when the controller observes a change to the desiredConfig in the configVersion of the machine config node spec. + format: int64 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: observedGeneration must not decrease + rule: self >= oldSelf + pinnedImageSets: + description: pinnedImageSets describes the current and desired pinned + image sets for this node. + items: + description: MachineConfigNodeStatusPinnedImageSet holds information + about the current, desired, and failed pinned image sets for the + observed machine config node. + properties: + currentGeneration: + description: currentGeneration is the generation of the pinned + image set that has most recently been successfully pulled + and pinned on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: currentGeneration must not decrease + rule: self >= oldSelf + desiredGeneration: + description: desiredGeneration is the generation of the pinned + image set that is targeted to be pulled and pinned on this + node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: desiredGeneration must not decrease + rule: self >= oldSelf + lastFailedGeneration: + description: lastFailedGeneration is the generation of the most + recent pinned image set that failed to be pulled and pinned + on this node. + format: int32 + minimum: 1 + type: integer + x-kubernetes-validations: + - message: lastFailedGeneration must not decrease + rule: self >= oldSelf + lastFailedGenerationError: + description: |- + lastFailedGenerationError is the error explaining why the desired images failed to be pulled and pinned. + The error is an empty string if the image pull and pin is successful. + maxLength: 32768 + type: string + name: + description: |- + name is the name of the pinned image set. + Must be a lowercase RFC-1123 subdomain name (https://tools.ietf.org/html/rfc1123) consisting + of only lowercase alphanumeric characters, hyphens (-), and periods (.), and must start and end + with an alphanumeric character, and be at most 253 characters in length. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: a lowercase RFC 1123 subdomain must consist of lower + case alphanumeric characters, '-' or '.', and must start + and end with an alphanumeric character. + rule: '!format.dns1123Subdomain().validate(self).hasValue()' + required: + - name + type: object + x-kubernetes-validations: + - message: desired generation must be greater than or equal to the + current generation + rule: 'has(self.desiredGeneration) && has(self.currentGeneration) + ? self.desiredGeneration >= self.currentGeneration : true' + - message: desired generation must be greater than or equal to the + last failed generation + rule: 'has(self.lastFailedGeneration) && has(self.desiredGeneration) + ? self.desiredGeneration >= self.lastFailedGeneration : true' + - message: last failed generation error must be defined on image + pull and pin failure + rule: 'has(self.lastFailedGeneration) ? has(self.lastFailedGenerationError) + : true' + maxItems: 100 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + x-kubernetes-validations: + - message: spec.node.name should match metadata.name + rule: self.metadata.name == self.spec.node.name + served: true + storage: true + subresources: + status: {} diff --git a/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-Default.yaml b/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-Default.yaml index d3ec99985fc..af77a8c9da1 100644 --- a/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-Default.yaml +++ b/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-Default.yaml @@ -245,9 +245,6 @@ { "name": "NoOverlayMode" }, - { - "name": "NoRegistryClusterInstall" - }, { "name": "NutanixMultiSubnets" }, @@ -355,6 +352,9 @@ { "name": "NewOLMWebhookProviderOpenshiftServiceCA" }, + { + "name": "NoRegistryClusterInstall" + }, { "name": "OSStreams" }, diff --git a/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-OKD.yaml b/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-OKD.yaml index 326fe926316..995007851b2 100644 --- a/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-OKD.yaml +++ b/payload-manifests/featuregates/featureGate-4-10-SelfManagedHA-OKD.yaml @@ -247,9 +247,6 @@ { "name": "NoOverlayMode" }, - { - "name": "NoRegistryClusterInstall" - }, { "name": "NutanixMultiSubnets" }, @@ -357,6 +354,9 @@ { "name": "NewOLMWebhookProviderOpenshiftServiceCA" }, + { + "name": "NoRegistryClusterInstall" + }, { "name": "OSStreams" }, From d832c607f0d2b135170ffc92463eb3608a465070 Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Thu, 16 Jul 2026 14:48:02 -0400 Subject: [PATCH 2/4] Update featuregate-test-analyzer output for Install features Augment `verify-feature-promotion` output to indicate pass percentage for `install should succeed` tests for featuregates that include "Install" in their name. This update gives a better indication of whether Install features are failing at installation or later during execution of e2e conformance tests. This update does not change the criteria for reporting success but adds more information in the output for easier analysis of feature state. --- .../codegen/cmd/featuregate-test-analyzer.go | 237 ++++++++++++++++-- .../cmd/featuregate-test-analyzer_test.go | 185 +++++++++++++- tools/codegen/pkg/sippy/json_types.go | 98 ++++++++ 3 files changed, 499 insertions(+), 21 deletions(-) diff --git a/tools/codegen/cmd/featuregate-test-analyzer.go b/tools/codegen/cmd/featuregate-test-analyzer.go index 9eda83a226b..175fcebae7b 100644 --- a/tools/codegen/cmd/featuregate-test-analyzer.go +++ b/tools/codegen/cmd/featuregate-test-analyzer.go @@ -35,6 +35,9 @@ const ( // required pass rate. // nearly all current tests pass 99% of the time, but in a two week window we lack enough data to say. requiredPassRateOfTestsPerVariant = 0.95 + + // required pass rate for "install should succeed" test + requiredPassRateForInstallTest = 1.0 ) type FeatureGateTestAnalyzerOptions struct { @@ -181,7 +184,7 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { clusterProfiles := recentlyEnabledFeatureGatesToClusterProfiles[enabledFeatureGate] md.Title(1, enabledFeatureGate) - testingResults, err := listTestResultFor(enabledFeatureGate, clusterProfiles) + testingResults, installTestLevelData, err := listTestResultFor(enabledFeatureGate, clusterProfiles) if err != nil { return err } @@ -190,7 +193,7 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { validationResults := checkIfTestingIsSufficient(enabledFeatureGate, testingResults) - // Separate warnings from blocking errors + // Separate warnings and blocking errors blockingErrors := []error{} warnings := []error{} for _, vr := range validationResults { @@ -201,6 +204,38 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { } } + // For Install feature gates, report "install should succeed: overall" test statistics first + if strings.Contains(enabledFeatureGate, "Install") { + md.Text("") + fmt.Fprintf(o.Out, "\n") + md.Textf("**Install test statistics for \"install should succeed: overall\":**\n") + fmt.Fprintf(o.Out, "Install test statistics for \"install should succeed: overall\":\n") + jobVariants := make([]JobVariant, 0, len(testingResults)) + for jobVariant := range testingResults { + jobVariants = append(jobVariants, jobVariant) + } + sort.Slice(jobVariants, func(i, j int) bool { + return jobVariants[i].String() < jobVariants[j].String() + }) + for _, jobVariant := range jobVariants { + installTest := installTestLevelData[jobVariant] + if installTest == nil { + md.Textf(" - %v: test not found\n", jobVariant) + fmt.Fprintf(o.Out, " %v: test not found\n", jobVariant) + } else if installTest.TotalRuns > 0 { + passPercent := float32(installTest.SuccessfulRuns) / float32(installTest.TotalRuns) + displayActual := int(passPercent * 100) + md.Textf(" - %v: passed %d%% (%d/%d runs)\n", jobVariant, displayActual, installTest.SuccessfulRuns, installTest.TotalRuns) + fmt.Fprintf(o.Out, " %v: passed %d%% (%d/%d runs)\n", jobVariant, displayActual, installTest.SuccessfulRuns, installTest.TotalRuns) + } else { + md.Textf(" - %v: 0 runs\n", jobVariant) + fmt.Fprintf(o.Out, " %v: 0 runs\n", jobVariant) + } + } + md.Text("") + fmt.Fprintf(o.Out, "\n") + } + if len(validationResults) == 0 { md.Textf("Sufficient CI testing for %q.\n", enabledFeatureGate) fmt.Fprintf(o.Out, "Sufficient CI testing for %q.\n", enabledFeatureGate) @@ -208,17 +243,23 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { if len(blockingErrors) > 0 { md.Textf("INSUFFICIENT CI testing for %q.\n", enabledFeatureGate) fmt.Fprintf(o.Out, "INSUFFICIENT CI testing for %q.\n", enabledFeatureGate) - } else { + } else if len(warnings) > 0 { md.Textf("CI testing issues found for %q (non-blocking warnings).\n", enabledFeatureGate) fmt.Fprintf(o.Out, "CI testing issues found for %q (non-blocking warnings).\n", enabledFeatureGate) + } else { + md.Textf("Sufficient CI testing for %q.\n", enabledFeatureGate) + fmt.Fprintf(o.Out, "Sufficient CI testing for %q.\n", enabledFeatureGate) } - md.Textf("* At least five tests are expected for a feature\n") - md.Textf("* Tests must be be run on every TechPreview platform (ask for an exception if your feature doesn't support a variant)") - md.Textf("* All tests must run at least 14 times on every platform") - md.Textf("* All tests must pass at least 95%% of the time") - md.Textf("* JobTier must be one of: standard, informing, blocking, candidate (candidate is allowed but produces a warning as it is not covered by Component Readiness)\n") - md.Text("") + if len(blockingErrors) > 0 || len(warnings) > 0 { + md.Textf("* At least five tests are expected for a feature\n") + md.Textf("* Tests must be be run on every TechPreview platform (ask for an exception if your feature doesn't support a variant)") + md.Textf("* All tests must run at least 14 times on every platform") + md.Textf("* All tests must pass at least 95%% of the time") + md.Textf("* For Install feature gates, the \"install should succeed: overall\" test must pass at least 100%% of the time") + md.Textf("* JobTier must be one of: standard, informing, blocking, candidate (candidate is allowed but produces a warning as it is not covered by Component Readiness)\n") + md.Text("") + } if len(warnings) > 0 { md.Textf("**Non-blocking warnings (optional variants):**\n") @@ -394,6 +435,11 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian } for _, testResults := range testedVariant.TestResults { + // Skip "install should succeed: overall" for Install feature gates - it has special validation below + if strings.Contains(featureGate, "Install") && testResults.TestName == "install should succeed: overall" { + continue + } + if testResults.TotalRuns < requiredNumberOfTestRunsPerVariant { results = append(results, ValidationResult{ Error: fmt.Errorf("error: %q only has %d runs, need at least %d runs for %q on %v", @@ -415,6 +461,38 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian }) } } + + // For Install feature gates, validate "install should succeed: overall" test + if strings.Contains(featureGate, "Install") { + installTest := testResultByName(testedVariant.TestResults, "install should succeed: overall") + if installTest == nil { + results = append(results, ValidationResult{ + Error: fmt.Errorf("warning: \"install should succeed: overall\" test not found for Install feature gate %q on %v", + featureGate, jobVariant), + IsWarning: true, + }) + } else { + if installTest.TotalRuns < requiredNumberOfTestRunsPerVariant { + results = append(results, ValidationResult{ + Error: fmt.Errorf("error: \"install should succeed: overall\" only has %d runs, need at least %d runs for %q on %v", + installTest.TotalRuns, requiredNumberOfTestRunsPerVariant, featureGate, jobVariant), + IsWarning: isOptional, + }) + } + if installTest.TotalRuns > 0 { + passPercent := float32(installTest.SuccessfulRuns) / float32(installTest.TotalRuns) + if passPercent < requiredPassRateForInstallTest { + displayExpected := int(requiredPassRateForInstallTest * 100) + displayActual := int(passPercent * 100) + results = append(results, ValidationResult{ + Error: fmt.Errorf("error: \"install should succeed: overall\" only passed %d%%, need at least %d%% for %q on %v", + displayActual, displayExpected, featureGate, jobVariant), + IsWarning: isOptional, + }) + } + } + } + } } return results @@ -622,6 +700,20 @@ type JobVariant struct { Optional bool // If true, validation failures for this variant are non-blocking warnings } +func (jv JobVariant) String() string { + result := fmt.Sprintf("cloud=%s arch=%s topology=%s", jv.Cloud, jv.Architecture, jv.Topology) + if jv.NetworkStack != "" { + result += fmt.Sprintf(" network=%s", jv.NetworkStack) + } + if jv.OS != "" { + result += fmt.Sprintf(" os=%s", jv.OS) + } + if jv.Optional { + result += " optional=true" + } + return result +} + type OrderedJobVariants []JobVariant func (a OrderedJobVariants) Len() int { return len(a) } @@ -694,6 +786,7 @@ type TestResults struct { type ValidationResult struct { Error error IsWarning bool // if true, this is a non-blocking warning (for optional variants) + IsInfo bool // if true, this is informational telemetry (e.g., install test pass percentage) } func testResultByName(results []TestResults, testName string) *TestResults { @@ -736,10 +829,11 @@ func validateJobTiers(jobVariant JobVariant) error { return nil } -func listTestResultFor(featureGate string, clusterProfiles sets.Set[string]) (map[JobVariant]*TestingResults, error) { +func listTestResultFor(featureGate string, clusterProfiles sets.Set[string]) (map[JobVariant]*TestingResults, map[JobVariant]*TestResults, error) { fmt.Printf("Query sippy for all test run results for feature gate %q on clusterProfile %q\n", featureGate, sets.List(clusterProfiles)) results := map[JobVariant]*TestingResults{} + installTestLevelData := map[JobVariant]*TestResults{} var jobVariantsToCheck []JobVariant if clusterProfiles.Has("Hypershift") && !nonHypershiftPlatforms.MatchString(featureGate) { @@ -760,19 +854,28 @@ func listTestResultFor(featureGate string, clusterProfiles sets.Set[string]) (ma // Validate all variants before making expensive API calls for _, jobVariant := range jobVariantsToCheck { if err := validateJobTiers(jobVariant); err != nil { - return nil, err + return nil, nil, err } } for _, jobVariant := range jobVariantsToCheck { jobVariantResults, err := listTestResultForVariant(featureGate, jobVariant) if err != nil { - return nil, err + return nil, nil, err } results[jobVariant] = jobVariantResults + + // For Install feature gates, also get test-level data for "install should succeed: overall" + if strings.Contains(featureGate, "Install") { + installTestData, err := getInstallTestLevelData(featureGate, jobVariant) + if err != nil { + return nil, nil, err + } + installTestLevelData[jobVariant] = installTestData + } } - return results, nil + return results, installTestLevelData, nil } func filterVariants(featureGate string, variantsList ...[]JobVariant) []JobVariant { @@ -862,15 +965,111 @@ func getRelease() (string, error) { return getLatestRelease() } -func listTestResultForVariant(featureGate string, jobVariant JobVariant) (*TestingResults, error) { - // Substring here matches for both [OCPFeatureGate:...] and [FeatureGate:...] - testPattern := fmt.Sprintf("FeatureGate:%s]", featureGate) +func getInstallTestLevelData(featureGate string, jobVariant JobVariant) (*TestResults, error) { + testPattern := "install should succeed: overall" + queries := sippy.QueriesForWithCapability(jobVariant.Cloud, jobVariant.Architecture, jobVariant.Topology, + jobVariant.NetworkStack, jobVariant.OS, jobVariant.JobTiers, testPattern, featureGate) + + defaultTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + + sippyClient := &http.Client{ + Timeout: 2 * time.Minute, + Transport: defaultTransport, + } + + release, err := getRelease() + if err != nil { + return nil, fmt.Errorf("couldn't fetch latest release version: %w", err) + } + + var installTestResult *TestResults + for _, currQuery := range queries { + currURL := &url.URL{ + Scheme: "https", + Host: "sippy.dptools.openshift.org", + Path: "api/tests", + } + queryParams := currURL.Query() + queryParams.Add("release", release) + queryParams.Add("period", "default") + filterJSON, err := json.Marshal(currQuery) + if err != nil { + return nil, err + } + queryParams.Add("filter", string(filterJSON)) + currURL.RawQuery = queryParams.Encode() + + req, err := http.NewRequest(http.MethodGet, currURL.String(), nil) + if err != nil { + return nil, err + } + + response, err := sippyClient.Do(req) + if err != nil { + return nil, err + } + if response.StatusCode < 200 || response.StatusCode > 299 { + return nil, fmt.Errorf("error getting sippy results (status=%d) for: %v", response.StatusCode, currURL.String()) + } + queryResultBytes, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + response.Body.Close() + testInfos := []sippy.SippyTestInfo{} + if err := json.Unmarshal(queryResultBytes, &testInfos); err != nil { + return nil, err + } + + for _, currTest := range testInfos { + if installTestResult == nil { + installTestResult = &TestResults{ + TestName: currTest.Name, + } + } + + // Accumulate results across multiple JobTier queries + if currTest.CurrentRuns >= requiredNumberOfTestRunsPerVariant { + installTestResult.TotalRuns += currTest.CurrentRuns + installTestResult.SuccessfulRuns += currTest.CurrentSuccesses + installTestResult.FailedRuns += currTest.CurrentFailures + installTestResult.FlakedRuns += currTest.CurrentFlakes + } else { + installTestResult.TotalRuns += currTest.CurrentRuns + currTest.PreviousRuns + installTestResult.SuccessfulRuns += currTest.CurrentSuccesses + currTest.PreviousSuccesses + installTestResult.FailedRuns += currTest.CurrentFailures + currTest.PreviousFailures + installTestResult.FlakedRuns += currTest.CurrentFlakes + currTest.PreviousFlakes + } + } + } + + return installTestResult, nil +} + +func listTestResultForVariant(featureGate string, jobVariant JobVariant) (*TestingResults, error) { // Feature gates used by the installer don't need separate tests, use the overall install tests if strings.Contains(featureGate, "Install") { return verifyJobBasedFeatureGatePromotion(featureGate, jobVariant) } + var testPattern string + var queries []*sippy.SippyQueryStruct + + // Substring here matches for both [OCPFeatureGate:...] and [FeatureGate:...] + testPattern = fmt.Sprintf("FeatureGate:%s]", featureGate) + queries = sippy.QueriesFor(jobVariant.Cloud, jobVariant.Architecture, jobVariant.Topology, + jobVariant.NetworkStack, jobVariant.OS, jobVariant.JobTiers, testPattern) fmt.Printf("Query sippy for all test run results for pattern %q on variant %#v\n", testPattern, jobVariant) defaultTransport := &http.Transport{ @@ -892,12 +1091,10 @@ func listTestResultForVariant(featureGate string, jobVariant JobVariant) (*Testi testNameToResults := map[string]*TestResults{} hasCandidateTierResults := false - queries := sippy.QueriesFor(jobVariant.Cloud, jobVariant.Architecture, jobVariant.Topology, jobVariant.NetworkStack, jobVariant.OS, jobVariant.JobTiers, testPattern) release, err := getRelease() if err != nil { return nil, fmt.Errorf("couldn't fetch latest release version: %w", err) } - fmt.Printf("Querying sippy release %s for test run results\n", release) for _, currQuery := range queries { currURL := &url.URL{ @@ -1139,7 +1336,7 @@ func getJobsForFeatureGateFromSippy(client *http.Client, release, featureGate st defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected a 200 OK status code but got %s", resp.StatusCode) + return nil, fmt.Errorf("expected a 200 OK status code but got %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) @@ -1165,7 +1362,7 @@ func getJobRunsFromSippy(client *http.Client, release, jobName string) ([]sippy. defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("expected a 200 OK status code but got %s", resp.StatusCode) + return nil, fmt.Errorf("expected a 200 OK status code but got %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) diff --git a/tools/codegen/cmd/featuregate-test-analyzer_test.go b/tools/codegen/cmd/featuregate-test-analyzer_test.go index 24d2bad6c20..2c22e5da4e9 100644 --- a/tools/codegen/cmd/featuregate-test-analyzer_test.go +++ b/tools/codegen/cmd/featuregate-test-analyzer_test.go @@ -65,7 +65,7 @@ func Test_listTestResultFor(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Skip("this is for ease of manual testing") - got, err := listTestResultFor(tt.args.featureGate, sets.New[string](tt.args.clusterProfile)) + got, _, err := listTestResultFor(tt.args.featureGate, sets.New[string](tt.args.clusterProfile)) if (err != nil) != tt.wantErr { t.Errorf("listTestResultFor() error = %v, wantErr %v", err, tt.wantErr) return @@ -481,6 +481,189 @@ func Test_checkIfTestingIsSufficient_OptionalVariants(t *testing.T) { } } +func Test_checkIfTestingIsSufficient_InstallFeatureGates(t *testing.T) { + tests := []struct { + name string + featureGate string + testingResults map[JobVariant]*TestingResults + wantBlockingErrors int + wantWarnings int + }{ + { + name: "Install feature gate with install should succeed: overall test failing 100% requirement", + featureGate: "FakeInstallFeature", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "metal", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 20, SuccessfulRuns: 19}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 1, // Blocking error: install should succeed: overall must pass at 100% + wantWarnings: 0, + }, + { + name: "Install feature gate without install should succeed: overall test - warning reported", + featureGate: "MockInstallGate", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "metal", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test5", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 0, + wantWarnings: 1, // Warning about missing "install should succeed: overall" test + }, + { + name: "Non-Install feature gate with install should succeed: overall test - no special validation", + featureGate: "SomeOtherFeature", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "aws", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 20, SuccessfulRuns: 19}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 0, + wantWarnings: 0, + }, + { + name: "Install feature gate with 100% pass rate - no errors", + featureGate: "FakeInstallFeature", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "aws", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 20, SuccessfulRuns: 20}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 0, + wantWarnings: 0, + }, + { + name: "Install feature gate with multiple variants - one fails 100% requirement", + featureGate: "FakeInstallFeature", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "metal", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 20, SuccessfulRuns: 19}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + { + Cloud: "metal", + Architecture: "amd64", + Topology: "single", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 18, SuccessfulRuns: 18}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 1, // One variant (ha) fails 100% requirement + wantWarnings: 0, + }, + { + name: "Install feature gate with insufficient runs for install test - blocking error", + featureGate: "MockInstallGate", + testingResults: map[JobVariant]*TestingResults{ + { + Cloud: "aws", + Architecture: "amd64", + Topology: "ha", + }: { + TestResults: []TestResults{ + {TestName: "install should succeed: overall", TotalRuns: 10, SuccessfulRuns: 10}, + {TestName: "test1", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test2", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test3", TotalRuns: 15, SuccessfulRuns: 15}, + {TestName: "test4", TotalRuns: 15, SuccessfulRuns: 15}, + }, + }, + }, + wantBlockingErrors: 1, // Blocking error for insufficient runs (< 14) + wantWarnings: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results := checkIfTestingIsSufficient(tt.featureGate, tt.testingResults) + + blockingErrors := 0 + warnings := 0 + for _, result := range results { + if result.IsWarning { + warnings++ + } else { + blockingErrors++ + } + } + + if blockingErrors != tt.wantBlockingErrors { + t.Errorf("got %d blocking errors, want %d", blockingErrors, tt.wantBlockingErrors) + for _, result := range results { + if !result.IsWarning { + t.Logf(" Blocking error: %v", result.Error) + } + } + } + if warnings != tt.wantWarnings { + t.Errorf("got %d warnings, want %d", warnings, tt.wantWarnings) + for _, result := range results { + if result.IsWarning { + t.Logf(" Warning: %v", result.Error) + } + } + } + }) + } +} + func Test_defaultQueriesIncludeCandidateTier(t *testing.T) { // When JobTiers is empty, QueriesFor should generate queries for all tiers // including candidate. This test is added to prevent regressions for candidate-tier diff --git a/tools/codegen/pkg/sippy/json_types.go b/tools/codegen/pkg/sippy/json_types.go index ad2012a3ef5..2242eab6ac9 100644 --- a/tools/codegen/pkg/sippy/json_types.go +++ b/tools/codegen/pkg/sippy/json_types.go @@ -179,6 +179,104 @@ func QueriesFor(cloud, architecture, topology, networkStack, os, jobTiers, testP return queries } +func QueriesForWithCapability(cloud, architecture, topology, networkStack, os, jobTiers, testPattern, capability string) []*SippyQueryStruct { + // Build base query items that are common to all JobTier queries + baseItems := []SippyQueryItem{ + { + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("Platform:%s", cloud), + }, + { + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("Architecture:%s", architecture), + }, + { + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("Topology:%s", topology), + }, + { + ColumnField: "name", + Not: false, + OperatorValue: "contains", + Value: testPattern, + }, + { + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("Capability:%s", capability), + }, + } + + if networkStack != "" { + baseItems = append(baseItems, SippyQueryItem{ + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("NetworkStack:%s", networkStack), + }) + } + + if os != "" { + baseItems = append(baseItems, SippyQueryItem{ + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("OS:%s", os), + }) + } + + // Parse JobTiers - comma-separated string, default to standard/informing/blocking/candidate if empty + var jobTiersList []string + if jobTiers == "" { + jobTiersList = []string{"standard", "informing", "blocking", "candidate"} + } else { + // Split by comma, trim whitespace, and deduplicate using sets + tierSet := sets.New[string]() + for _, tier := range strings.Split(jobTiers, ",") { + if trimmed := strings.TrimSpace(tier); trimmed != "" { + tierSet.Insert(trimmed) + } + } + // If all tiers were whitespace/empty after trimming, use defaults + if tierSet.Len() == 0 { + jobTiersList = []string{"standard", "informing", "blocking", "candidate"} + } else { + jobTiersList = sets.List(tierSet) + } + } + + // Generate one query per JobTier (to work around API's single LinkOperator limitation) + var queries []*SippyQueryStruct + for _, jobTier := range jobTiersList { + // Copy base items for this query + items := make([]SippyQueryItem, len(baseItems)) + copy(items, baseItems) + + // Add JobTier filter + items = append(items, SippyQueryItem{ + ColumnField: "variants", + Not: false, + OperatorValue: "contains", + Value: fmt.Sprintf("JobTier:%s", jobTier), + }) + + queries = append(queries, &SippyQueryStruct{ + Items: items, + LinkOperator: "and", + TierName: jobTier, + }) + } + + return queries +} + func BuildSippyTestAnalysisURL(release, testName, topology, cloud, architecture, networkStack, os string) string { filterItems := []SippyQueryItem{ { From a59e7e050ddcacfc1478838462b8e8480716a153 Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Mon, 20 Jul 2026 16:43:41 -0400 Subject: [PATCH 3/4] Always include metal single and compact variants as optional checks --- .../codegen/cmd/featuregate-test-analyzer.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tools/codegen/cmd/featuregate-test-analyzer.go b/tools/codegen/cmd/featuregate-test-analyzer.go index 175fcebae7b..5e36b50b18b 100644 --- a/tools/codegen/cmd/featuregate-test-analyzer.go +++ b/tools/codegen/cmd/featuregate-test-analyzer.go @@ -217,7 +217,13 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { sort.Slice(jobVariants, func(i, j int) bool { return jobVariants[i].String() < jobVariants[j].String() }) + prevCloud := "" for _, jobVariant := range jobVariants { + if prevCloud != "" && jobVariant.Cloud != prevCloud { + md.Text("") + fmt.Fprintf(o.Out, "\n") + } + prevCloud = jobVariant.Cloud installTest := installTestLevelData[jobVariant] if installTest == nil { md.Textf(" - %v: test not found\n", jobVariant) @@ -677,6 +683,16 @@ var ( NetworkStack: "dual", JobTiers: "candidate,standard,informing,blocking", }, + { + Cloud: "metal", + Architecture: "amd64", + Topology: "single", + }, + { + Cloud: "metal", + Architecture: "amd64", + Topology: "compact", + }, } nonHypershiftPlatforms = regexp.MustCompile("(?i)nutanix|metal|vsphere|openstack|azure|gcp") @@ -849,6 +865,13 @@ func listTestResultFor(featureGate string, clusterProfiles sets.Set[string]) (ma } jobVariantsToCheck = append(jobVariantsToCheck, selfManagedPlatformVariants...) + + // Always include metal single and compact variants as optional checks + for _, v := range optionalSelfManagedPlatformVariants { + if strings.ToLower(v.Cloud) == "metal" && (strings.ToLower(v.Topology) == "single" || strings.ToLower(v.Topology) == "compact") { + jobVariantsToCheck = append(jobVariantsToCheck, v) + } + } } // Validate all variants before making expensive API calls From fd7e0ed29363e6c534c53fa82bfebd3cb5ace4a9 Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Wed, 22 Jul 2026 12:30:10 -0400 Subject: [PATCH 4/4] Exclude job runs with internal and external infrastrcuture failures Ignore runs with internal and external infrastrcuture failures while calculating pass percentages. --- .../codegen/cmd/featuregate-test-analyzer.go | 101 +++++++++++++++--- 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/tools/codegen/cmd/featuregate-test-analyzer.go b/tools/codegen/cmd/featuregate-test-analyzer.go index 5e36b50b18b..a433d01a7d5 100644 --- a/tools/codegen/cmd/featuregate-test-analyzer.go +++ b/tools/codegen/cmd/featuregate-test-analyzer.go @@ -196,11 +196,14 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { // Separate warnings and blocking errors blockingErrors := []error{} warnings := []error{} + var blockingResults, warningResults []ValidationResult for _, vr := range validationResults { if vr.IsWarning { warnings = append(warnings, vr.Error) + warningResults = append(warningResults, vr) } else { blockingErrors = append(blockingErrors, vr.Error) + blockingResults = append(blockingResults, vr) } } @@ -269,24 +272,20 @@ func (o *FeatureGateTestAnalyzerOptions) Run(ctx context.Context) error { if len(warnings) > 0 { md.Textf("**Non-blocking warnings (optional variants):**\n") - for _, warn := range warnings { - md.Textf(" - %s\n", warn.Error()) - } + writeGroupedValidationResults(warningResults, md) md.Text("") } if len(blockingErrors) > 0 { md.Textf("**Blocking errors:**\n") - for _, err := range blockingErrors { - md.Textf(" - %s\n", err.Error()) - } + writeGroupedValidationResults(blockingResults, md) md.Text("") } md.Text("") } // Only add blocking errors to the error list (warnings don't fail the job) - errs = append(errs, blockingErrors...) + errs = append(errs, groupErrorsByCategory(blockingResults)...) featureGateHTMLData = append(featureGateHTMLData, buildHTMLFeatureGateData(enabledFeatureGate, testingResults, blockingErrors, release)) } @@ -429,14 +428,20 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian Error: fmt.Errorf("warning: variant %v includes test data from candidate-tier jobs which are not covered by Component Readiness and lack standard regression protection", jobVariant), IsWarning: true, + Category: CategoryCandidateTier, }) } + if len(testedVariant.TestResults) == 0 { + continue + } + if len(testedVariant.TestResults) < requiredNumberOfTests { results = append(results, ValidationResult{ Error: fmt.Errorf("error: only %d tests found, need at least %d for %q on %v", len(testedVariant.TestResults), requiredNumberOfTests, featureGate, jobVariant), IsWarning: isOptional, + Category: CategoryInsufficientTests, }) } @@ -450,7 +455,8 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian results = append(results, ValidationResult{ Error: fmt.Errorf("error: %q only has %d runs, need at least %d runs for %q on %v", testResults.TestName, testResults.TotalRuns, requiredNumberOfTestRunsPerVariant, featureGate, jobVariant), - IsWarning: isOptional, + IsWarning: isOptional, + Category: CategoryInsufficientRuns, }) } if testResults.TotalRuns == 0 { @@ -464,6 +470,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian Error: fmt.Errorf("error: %q only passed %d%%, need at least %d%% for %q on %v", testResults.TestName, displayActual, displayExpected, featureGate, jobVariant), IsWarning: isOptional, + Category: CategoryPassRate, }) } } @@ -476,6 +483,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian Error: fmt.Errorf("warning: \"install should succeed: overall\" test not found for Install feature gate %q on %v", featureGate, jobVariant), IsWarning: true, + Category: CategoryInstallTest, }) } else { if installTest.TotalRuns < requiredNumberOfTestRunsPerVariant { @@ -483,6 +491,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian Error: fmt.Errorf("error: \"install should succeed: overall\" only has %d runs, need at least %d runs for %q on %v", installTest.TotalRuns, requiredNumberOfTestRunsPerVariant, featureGate, jobVariant), IsWarning: isOptional, + Category: CategoryInstallTest, }) } if installTest.TotalRuns > 0 { @@ -494,6 +503,7 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian Error: fmt.Errorf("error: \"install should succeed: overall\" only passed %d%%, need at least %d%% for %q on %v", displayActual, displayExpected, featureGate, jobVariant), IsWarning: isOptional, + Category: CategoryInstallTest, }) } } @@ -504,6 +514,46 @@ func checkIfTestingIsSufficient(featureGate string, testingResults map[JobVarian return results } +func groupErrorsByCategory(results []ValidationResult) []error { + categoryOrder := []ValidationCategory{} + grouped := map[ValidationCategory][]ValidationResult{} + for _, vr := range results { + if _, seen := grouped[vr.Category]; !seen { + categoryOrder = append(categoryOrder, vr.Category) + } + grouped[vr.Category] = append(grouped[vr.Category], vr) + } + var errs []error + for i, cat := range categoryOrder { + if i > 0 { + errs = append(errs, fmt.Errorf("")) + } + for _, vr := range grouped[cat] { + errs = append(errs, vr.Error) + } + } + return errs +} + +func writeGroupedValidationResults(results []ValidationResult, md *utils.Markdown) { + categoryOrder := []ValidationCategory{} + grouped := map[ValidationCategory][]ValidationResult{} + for _, vr := range results { + if _, seen := grouped[vr.Category]; !seen { + categoryOrder = append(categoryOrder, vr.Category) + } + grouped[vr.Category] = append(grouped[vr.Category], vr) + } + for i, cat := range categoryOrder { + if i > 0 { + md.Text("") + } + for _, vr := range grouped[cat] { + md.Textf(" - %s\n", vr.Error.Error()) + } + } +} + func writeTestingMarkDown(testingResults map[JobVariant]*TestingResults, md *utils.Markdown) { jobVariantsSet := sets.KeySet(testingResults) jobVariants := jobVariantsSet.UnsortedList() @@ -798,11 +848,22 @@ type TestResults struct { FlakedRuns int } +type ValidationCategory string + +const ( + CategoryCandidateTier ValidationCategory = "candidate-tier" + CategoryInsufficientTests ValidationCategory = "insufficient-tests" + CategoryInsufficientRuns ValidationCategory = "insufficient-runs" + CategoryPassRate ValidationCategory = "pass-rate" + CategoryInstallTest ValidationCategory = "install-test" +) + // ValidationResult represents a validation error or warning type ValidationResult struct { Error error - IsWarning bool // if true, this is a non-blocking warning (for optional variants) - IsInfo bool // if true, this is informational telemetry (e.g., install test pass percentage) + IsWarning bool // if true, this is a non-blocking warning (for optional variants) + IsInfo bool // if true, this is informational telemetry (e.g., install test pass percentage) + Category ValidationCategory // groups related results together in output } func testResultByName(results []TestResults, testName string) *TestResults { @@ -1311,17 +1372,25 @@ func verifyJobPassRate(client *http.Client, release string, job sippy.SippyJob, return nil, fmt.Errorf("getting job %q results from sippy: %w", job.Name, err) } - testResults := &TestResults{ - TestName: job.Name, - TotalRuns: len(jobRuns), - } - triagedTestFailures, err := getTriagedTestFailuresFromSippy(client, release, variant) if err != nil { return nil, fmt.Errorf("getting triaged test failures from sippy: %w", err) } + fmt.Printf("\nIgnoring job runs that have internal or external infrastructure failures from our analysis.\n\n") + + infraFailures := 0 + testResults := &TestResults{ + TestName: job.Name, + TotalRuns: len(jobRuns), + } + for _, jobRun := range jobRuns { + if jobRun.OverallResult == "N" || jobRun.OverallResult == "n" { + infraFailures++ + continue + } + if jobRun.OverallResult == "F" && !jobRun.KnownFailure { untriagedTestFailures := []string{} @@ -1348,6 +1417,8 @@ func verifyJobPassRate(client *http.Client, release string, job sippy.SippyJob, testResults.SuccessfulRuns++ } + testResults.TotalRuns -= infraFailures + return testResults, nil }