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"
},
diff --git a/tools/codegen/cmd/featuregate-test-analyzer.go b/tools/codegen/cmd/featuregate-test-analyzer.go
index 9eda83a226b..a433d01a7d5 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,17 +193,58 @@ 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{}
+ 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)
}
}
+ // 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()
+ })
+ 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)
+ 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,38 +252,40 @@ 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")
- 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))
}
@@ -382,23 +428,35 @@ 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,
})
}
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",
testResults.TestName, testResults.TotalRuns, requiredNumberOfTestRunsPerVariant, featureGate, jobVariant),
- IsWarning: isOptional,
+ IsWarning: isOptional,
+ Category: CategoryInsufficientRuns,
})
}
if testResults.TotalRuns == 0 {
@@ -412,14 +470,90 @@ 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,
})
}
}
+
+ // 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,
+ Category: CategoryInstallTest,
+ })
+ } 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,
+ Category: CategoryInstallTest,
+ })
+ }
+ 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,
+ Category: CategoryInstallTest,
+ })
+ }
+ }
+ }
+ }
}
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()
@@ -599,6 +733,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")
@@ -622,6 +766,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) }
@@ -690,10 +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)
+ 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 {
@@ -736,10 +906,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) {
@@ -755,24 +926,40 @@ 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
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 +1049,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 +1175,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{
@@ -1091,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{}
@@ -1128,6 +1417,8 @@ func verifyJobPassRate(client *http.Client, release string, job sippy.SippyJob,
testResults.SuccessfulRuns++
}
+ testResults.TotalRuns -= infraFailures
+
return testResults, nil
}
@@ -1139,7 +1430,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 +1456,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{
{