diff --git a/OOTDRIVER.md b/OOTDRIVER.md new file mode 100644 index 0000000..fe508ac --- /dev/null +++ b/OOTDRIVER.md @@ -0,0 +1,264 @@ +# Out-of-Tree Xe Driver + +Intel GPU Base operator leverages [Kernel Module Management](https://github.com/kubernetes-sigs/kernel-module-management) (KMM) operator to install out-of-tree (OoT) driver to nodes. The ClusterPolicy CRD has a simplified version of the KMM's Module CRD included under `kernelModule` field in ClusterPolicy. + +The OoT Xe driver project is located [here](https://github.com/intel-gpu/xekmd-backports/tree/releases/main). + +## Prerequisites + +* Kernel module management operator must be installed to the cluster + * Intel GPU base operator won't try to deploy the Module CR if it cannot find KMM + * KMM is detected once at operator startup by looking for the `kmm.sigs.x-k8s.io` API group. If KMM is installed after the operator, restart the operator deployment. Until then the `kernelModule` field is ignored and the ClusterPolicy reports `KMM is not installed in the cluster.` in `status.errors`. +* Container registry for storing kernel driver containers + * Use harbor, docker's registry, or OpenShift's ImageStream + * The registry must be writable: no prebuilt driver containers are published, so the images are always built in-cluster +* On OpenShift, install the operator with `openshift.enabled=true` + * This creates the `-module-loader-scc` SecurityContextConstraints (privileged, `SYS_MODULE`) that the module loader Pods need. + +## Supported OSs + +Currently the OoT KMD only supports Ubuntu 26.04. + +## What the operator creates + +When `kernelModule` is set in the ClusterPolicy, the operator creates a single KMM `Module` (`kmm.sigs.x-k8s.io/v1beta1`) named `-gpu` in the operator's namespace. The Module is owned by the ClusterPolicy, so it is removed automatically when the ClusterPolicy is deleted. + +A few ClusterPolicy fields are shared with the Module: + +* `spec.pullSecret` is used as the Module's `imageRepoSecret` +* `spec.nodeSelector`, `spec.useNFDLabeling` and `spec.tolerations` are used for selecting and tolerating the target nodes +* The in-tree driver (`kernelModule.moduleName`, `xe` by default) is always added to the Module's `inTreeModulesToRemove` so the in-tree driver is unloaded before the OoT driver is loaded. + +## Usage + +Currently base operator only supports KMM's in-cluster builds. There are no prebuilt kernel mode driver (KMD) containers available. To leverage the OoT KMD, one has to have access to a registry that is used to store the KMD containers. + +To leverage base operator's KMM integration, fill in `kernelModule` object in the ClusterPolicy CR. There is an incomplete sample for Ubuntu 26.04 [here](config/samples/dra-kmm-ubuntu26.04/). Incomplete means that the container registry, pull secret and nodeSelector are not properly set and need to be filled. + +### pullSecret + +Typically container registries are access controlled. Use the `pullSecret` field with a secret name that contains credentials to be able to push the built KMD container(s). + +``` +spec: + pullSecret: + name: <> +``` + +The secret must be in the operator's namespace. With in-cluster builds the credentials need push access to the registry, not just pull. + +Creating a pull secret from an existing docker config.json: + +``` +kubectl create secret generic my-registry \ + --namespace intel-gpu-base-operator \ + --type=kubernetes.io/dockerconfigjson \ + --from-file=.dockerconfigjson="$HOME/.docker/config.json" +``` + +### nodeSelector + +Node selector is used to target nodes with Intel GPU hardware. For example, label nodes with `gpu-hw-wo-driver: true` and then use that as the nodeSelector. + +Node selector: +``` +spec: + nodeSelector: + gpu-hw-wo-driver: "true" +``` + +In addition to the labels given in `spec.nodeSelector`, the operator always requires `kubernetes.io/arch: amd64`. + +*Note:* It's also possible to use `spec.useNFDLabeling: true` which allows targeting nodes with Intel GPU PCI devices. It should be noted that if there are heterogeneous nodes in the cluster, it's best to label nodes manually. + +With `useNFDLabeling: true` the node selectors differ between the components: + +* The KMM Module targets `intel.feature.node.kubernetes.io/gpu-pci: "true"`, which only requires an Intel GPU PCI device to be present. The module loader Pods can therefore be scheduled on nodes that do not yet have a working GPU driver. +* Device plugin, DRA and XPU Manager target `intel.feature.node.kubernetes.io/gpu: "true"`, which requires a working driver. + +The `gpu-pci` label comes from the NFD rule in [config/deployments/nfd/node-feature-rules-gpu.yaml](config/deployments/nfd/node-feature-rules-gpu.yaml), so NFD and the GPU rule must be deployed for NFD based labeling to work. + +Additionally, whenever `kernelModule` is set, the device plugin, DRA and XPU Manager node selectors get the KMM readiness label `kmm.node.kubernetes.io/.-gpu.ready`. This way the GPU components are only started on nodes where the OoT driver has actually been loaded. + +### containerImage + +Container image field under the kernelMappings indicates the registry where the built KMD container will be pushed to (and pulled from). + +``` +spec: + kernelModule: + kernelMappings: + - regexp: '7.0.0-.*-generic' + containerImage: "<>:v7.1.4.6_260728.7-$KERNEL_FULL_VERSION" +``` + +Note: `$KERNEL_FULL_VERSION` is replaced by KMM to reflect the kernel version running on the nodes. Other KMM template variables (for example `$MOD_NAME` and the `${VAR}` form) can be used as well. + +Each `kernelMappings` entry needs: + +* `regexp`: a regular expression matched against the node's kernel version. Anchored patterns (`^7\.0\.0-.*-generic$`) give exact matches. +* `containerImage`, `build` or both. The image reference must include an explicit tag or digest. + +Changing `containerImage` is the recommended way to upgrade the driver. KMM rolls the new image out to all selected nodes at once, which briefly disrupts GPU workloads on those nodes while the module is reloaded. + +### build + +`build` enables KMM's in-cluster build. KMM builds and pushes the image if it is not found in the registry. + +``` +spec: + kernelModule: + kernelMappings: + - regexp: '7.0.0-.*-generic' + containerImage: "<>:v7.1.4.6_260728.7-$KERNEL_FULL_VERSION" + build: + dockerfileConfigMap: + name: xe-build-u26.04 + buildArgs: + - name: XE_TAG + value: xebr_v7.1.4.6_260728.7 +``` + +* `dockerfileConfigMap` must point to a ConfigMap in the operator's namespace with the Dockerfile stored under the `dockerfile` key. See [build-configmap.yaml](config/samples/dra-kmm-ubuntu26.04/build-configmap.yaml) for a working Ubuntu 26.04 example. +* `buildArgs` are passed to the image builder as build arguments. +* `secrets` are build time secrets, for example credentials for a private source repository. They are not used for registry authentication, use `spec.pullSecret` for that. + +The build Pods use the same node selector as the module itself, because building the driver requires the kernel headers and toolchain matching the kernel of the target nodes. + +### firmwarePath + +`firmwarePath` is the path inside the driver container that holds the firmware files. KMM copies the files from that path to the host's firmware search path before loading the module. + +``` +spec: + kernelModule: + firmwarePath: /firmware +``` + +Note: on containerd based clusters setting the firmware path currently fails, see the known issues below. + +### modulesLoadingOrder + +`modulesLoadingOrder` describes a softdep style loading order for drivers that consist of several modules. KMM loads the modules in the given order and unloads them in reverse order. + +``` +spec: + kernelModule: + modulesLoadingOrder: + - "xe" + - "drm_gpuvm" + - "drm_buddy" +``` + +The list must have at least two entries and the first entry must be the same as `kernelModule.moduleName` (`xe` by default). + +### registryTLS + +`registryTLS` is meant for self-hosted registries that use plain HTTP or a certificate that is not trusted by the cluster. + +``` +spec: + kernelModule: + registryTLS: + insecure: false + insecureSkipTLSVerify: true +``` + +The setting can also be given per kernel mapping, in which case it overrides the value given under `kernelModule`. + +### version and ordered upgrades + +`kernelModule.version` opts into KMM's [ordered upgrade](https://kmm.sigs.k8s.io/documentation/ordered_upgrade), which is meant for low-disruption, node-by-node driver rollouts. + +When `version` is set, KMM loads the driver on a node only after a cluster admin has labeled that node with the matching version label. Nodes without the label are left untouched, so nothing happens until the labels are added: + +``` +kubectl label node \ + kmm.node.kubernetes.io/version-module.intel-gpu-base-operator.gpu-policy-gpu=v7.1.4.6_260728.7 +``` + +The label format is `kmm.node.kubernetes.io/version-module..-gpu=`. This lets the admin drain GPU workloads from a node before the driver is reloaded on it. Because of Kubernetes limitations on label names, the combined length of .-gpu must not exceed 39 characters. + +Most deployments should leave `version` unset and upgrade by changing `containerImage` instead. + +Note: KMM's Module webhook does not allow adding or removing the version in place. When `version` is changed from empty to set, or from set to empty, the operator deletes and recreates the Module, which unloads the driver in the process. The ClusterPolicy reports `Recreating` in `status.kmmStatus` while this happens. + +## Verification + +When the ClusterPolicy is deployed, the status of the kernel module installation can be observed from the ClusterPolicy's status section. The `KMM` column shows how many of the targeted nodes have the module loaded. + +``` +$ kubectl get clusterpolicy +NAME DP DRA XPU KMM AGE +gpu-policy N/A 1/1 1/1 1/1 10m +``` + +``` +$ kubectl get clusterpolicy gpu-policy -o yaml +... +status: + draStatus: 1/1 + kmmStatus: 1/1 + xpuManagerStatus: 1/1 + devicePluginStatus: N/A +``` + +`kmmStatus` values: + +| Value | Meaning | +|-|-| +| `/` | Number of nodes where the module loader is available vs. targeted | +| `N/A` | `kernelModule` is not set in the ClusterPolicy | +| `Removing` | The Module is being deleted | +| `Recreating` | The Module is being recreated due to a `version` change | + +While the driver is not yet loaded on every targeted node, the reason is reported under `status.errors`: + +``` +status: + kmmStatus: 0/1 + errors: + - module loader not fully available (0/1) for modules.kmm.sigs.x-k8s.io/gpu-policy-gpu +``` + +Digging deeper, when the status doesn't reach the desired count: + +``` +# Module CR status and events +kubectl -n intel-gpu-base-operator get modules +kubectl -n intel-gpu-base-operator describe module gpu-policy-gpu + +# Build and module loader Pod logs +kubectl -n intel-gpu-base-operator get pods +kubectl -n intel-gpu-base-operator logs + +# KMM's node labels: build/loader progress and readiness +kubectl get node -o json | grep kmm.node.kubernetes.io + +# On the node itself: confirm the loaded driver is the OoT one +modinfo xe | head +``` + +## Removing the OoT driver + +Removing the `kernelModule` block from the ClusterPolicy, or deleting the ClusterPolicy, makes the operator delete the Module. KMM then unloads the OoT driver from the nodes. + +On DRA clusters the operator refuses to delete or recreate the Module while there are allocated GPU ResourceClaims, so that the driver is not pulled out from under running GPU workloads. The reconcile is retried and the reason is reported in the ClusterPolicy status: + +``` +status: + errors: + - allocated GPU ResourceClaims blocking modules.kmm.sigs.x-k8s.io/gpu-policy-gpu deletion +``` + +Remove the workloads holding the claims to let the removal proceed. Note that unloading can still fail if devices are bound to the driver, see the known issues below. + +## Known issues + +* containerd: KMM's worker image is using USER 201 to set firmware load path. Even if the container is running with `securityContext: privileged: true`, the worker Pod fails to set the path. This will block KMD install. + * KMM issue: https://github.com/kubernetes-sigs/kernel-module-management/issues/1337 +* Device using a kernel driver blocks module removal: Xe driver cannot be removed if there are devices using it. The devices have to unbind first. + * KMM has an enhancement to fix this: https://github.com/kubernetes-sigs/kernel-module-management/blob/main/docs/enhancements/0005-modprobed-config.md + * By blacklisting the in-tree KMD, it's possible to load OoT driver once. But removing the OoT driver then fails due to the same reason. +* There are no prebuilt KMD containers, so a registry that the cluster can push to is always required. +* Only Ubuntu 26.04 is supported, see [Supported OSs](#supported-oss). diff --git a/README.md b/README.md index acf1b8c..5412b90 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,14 @@ kubectl apply -k config/samples/dra/ The Intel GPU base operator supports updating the GPU firmware on the cluster nodes. The update is handled via a GPUFirmwareUpdate CRD. The update flow and details are explained in the [FW update documentation](FWUPDATE.md). +## OoT Kernel driver + +The base operator has preliminary support to use out-of-tree Xe kernel mode driver to either add the driver where it is missing, or update the driver to a newer version. + +The driver is installed via the [Kernel Module Management](https://github.com/kubernetes-sigs/kernel-module-management) (KMM) operator, which must be installed to the cluster. The driver containers are always built in-cluster, so a registry that the cluster can push to is also required. Currently only Ubuntu 26.04 is supported. + +Details of the functionality are provided in the [OOTDRIVER.md](OOTDRIVER.md) file. + ## Contributing [Contributions](CONTRIBUTING.md) to this project are welcome as issues (bugs, enhancement requests) or via pull requests. Please review our [Code of Conduct](CODE_OF_CONDUCT.md) and our note on [security policy](SECURITY.md). diff --git a/config/deployments/nfd/node-feature-rules-gpu.yaml b/config/deployments/nfd/node-feature-rules-gpu.yaml index e2c36c2..f33361f 100644 --- a/config/deployments/nfd/node-feature-rules-gpu.yaml +++ b/config/deployments/nfd/node-feature-rules-gpu.yaml @@ -29,6 +29,14 @@ spec: - feature: kernel.enabledmodule matchExpressions: xe: {op: Exists} + - name: "intel.gpu.without-driver" + labels: + "intel.feature.node.kubernetes.io/gpu-pci": "true" + matchFeatures: + - feature: pci.device + matchExpressions: + vendor: {op: In, value: ["8086"]} + class: {op: In, value: ["0300", "0380"]} # Assumes there are only one model of GPU per node. # source: https://dgpu-docs.intel.com/overview/supported-hardware/xe-driver-gpus.html - name: "intel.gpu.pro.b50" diff --git a/config/samples/dra-kmm-ubuntu26.04/build-configmap.yaml b/config/samples/dra-kmm-ubuntu26.04/build-configmap.yaml new file mode 100644 index 0000000..19395f9 --- /dev/null +++ b/config/samples/dra-kmm-ubuntu26.04/build-configmap.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +data: + dockerfile: | + FROM ubuntu:26.04 AS builder + + SHELL ["/bin/bash", "-o", "pipefail", "-c"] + + ARG KERNEL_FULL_VERSION + ARG XE_TAG + ARG XE_SHA + ARG FWFILES + ARG EXTRAMODULES + + RUN apt-get update && apt-get install --no-install-recommends -y \ + automake bc binutils bison ca-certificates coreutils debhelper devscripts \ + flex gawk gcc git gnupg gperf kmod yacc wget make m4 patch \ + libelf-dev \ + linux-headers-${KERNEL_FULL_VERSION} \ + linux-modules-${KERNEL_FULL_VERSION} \ + && rm -rf /var/lib/apt/lists/* + + WORKDIR /usr/src + + RUN mkdir -p xe-backports && \ + wget -q "https://github.com/intel-gpu/xekmd-backports/archive/refs/tags/${XE_TAG}.tar.gz" -O xe.tar.gz && \ + echo "${XE_SHA} xe.tar.gz" | sha256sum --check --status && \ + tar -xzf xe.tar.gz --strip-components=1 -C xe-backports && \ + rm -f xe.tar.gz && \ + cd xe-backports && \ + ./compile.sh compile && \ + cd src && \ + while IFS= read -r -d '' kmod; do \ + strip --strip-debug "$kmod"; \ + dest="/opt/lib/modules/${KERNEL_FULL_VERSION}/${kmod#./}"; \ + mkdir -p "$(dirname "$dest")"; \ + cp "$kmod" "$dest"; \ + done < <(find . -name "*.ko" -print0) + + # collect ko files from from the modules package and copy to /opt/lib/modules + RUN for mod in ${EXTRAMODULES}; do \ + find /lib/modules/${KERNEL_FULL_VERSION} -type f -name "${mod}*.ko*" -exec sh -c ' \ + for kmod; do \ + dest="/opt/lib/modules/${KERNEL_FULL_VERSION}/${kmod#./}"; \ + mkdir -p "$(dirname "$dest")"; \ + cp "$kmod" "$dest"; \ + done' sh {} +; \ + done + + RUN mkdir -p /usr/src/firmware-out/xe && \ + for fwfile in $FWFILES; do \ + wget -q "https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/xe/${fwfile}" -O "/usr/src/firmware-out/xe/${fwfile}"; \ + done + + FROM ubuntu:26.04 AS install + + ARG KERNEL_FULL_VERSION + + RUN apt-get update && apt-get install --no-install-recommends -y \ + ca-certificates \ + kmod \ + && rm -rf /var/lib/apt/lists/* + + COPY --from=builder /opt/lib/modules/${KERNEL_FULL_VERSION}/ /opt/lib/modules/${KERNEL_FULL_VERSION}/ + COPY --from=builder /usr/src/firmware-out/xe/ /firmware/xe/ + + RUN depmod -b /opt "${KERNEL_FULL_VERSION}" +kind: ConfigMap +metadata: + name: xe-build-u26.04 + namespace: intel-gpu-base-operator diff --git a/config/samples/dra-kmm-ubuntu26.04/clusterpolicy.yaml b/config/samples/dra-kmm-ubuntu26.04/clusterpolicy.yaml new file mode 100644 index 0000000..7fdbaab --- /dev/null +++ b/config/samples/dra-kmm-ubuntu26.04/clusterpolicy.yaml @@ -0,0 +1,47 @@ +apiVersion: intel.com/v1alpha1 +kind: ClusterPolicy +metadata: + name: gpu-policy +spec: + nodeSelector: + # example only, needs to be changed + gpu.intel.com/exists: "true" + + kernelModule: + firmwarePath: /firmware + # Setting version opts into KMM's ordered upgrade, where the driver is only + # loaded on nodes carrying a matching version-module label. See OOTDRIVER.md. + #version: v7.1.4.6_260728.7 + kernelMappings: + - build: + dockerfileConfigMap: + name: xe-build-u26.04 + buildArgs: + - name: XE_TAG + value: xebr_v7.1.4.8_260728.8 + - name: XE_SHA + value: 4b11801a7c15916ec0a8056cdb4fd64d94c6499f1b3e41847b70d2825c102db3 + - name: FWFILES + value: "bmg_guc_70.bin bmg_huc.bin fan_control_8086_e20b_8086_1100.bin" + - name: EXTRAMODULES + value: "drm_dma_helper drm_buddy drm_gpuvm drm_ttm_helper drm_suballoc_helper drm_exec" + containerImage: "registry.local/xe-oot-kmd:v7.1.4.8_260728.8-$KERNEL_FULL_VERSION" + regexp: '7.0.0-.*-generic' + inTreeModulesToRemove: ["xe","intel_pmc_core","pmt_telemetry", "pmt_discovery", "pmt_crashlog", "pmt_class", "intel-vsec"] + + modulesLoadingOrder: + - "xe" + - "pmt-telemetry" + - "pmt-discovery" + - "pmt-crashlog" + - "pmt-class" + - "intel-vsec" + - "drm_dma_helper" + - "drm_buddy" + - "drm_gpuvm" + - "gpu-sched" + - "drm_ttm_helper" + - "xe-compat" + - "ttm" + - "drm_suballoc_helper" + diff --git a/config/samples/dra-kmm-ubuntu26.04/kustomization.yaml b/config/samples/dra-kmm-ubuntu26.04/kustomization.yaml new file mode 100644 index 0000000..fc6d31d --- /dev/null +++ b/config/samples/dra-kmm-ubuntu26.04/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- ../dra +- build-configmap.yaml + +patches: +- path: clusterpolicy.yaml +# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/internal/controller/controller_utils.go b/internal/controller/controller_utils.go index 38e1cdb..e55416c 100644 --- a/internal/controller/controller_utils.go +++ b/internal/controller/controller_utils.go @@ -26,6 +26,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + nfdGpuWithDriver = "intel.feature.node.kubernetes.io/gpu" + nfdGpuWithoutDriver = "intel.feature.node.kubernetes.io/gpu-pci" +) + func generateNodeSelector(cp *v1alpha.ClusterPolicy, opts ControllerOpts) map[string]string { ns := map[string]string{ "kubernetes.io/arch": "amd64", @@ -38,7 +43,7 @@ func generateNodeSelector(cp *v1alpha.ClusterPolicy, opts ControllerOpts) map[st } if cp.Spec.UseNFDLabeling { - ns["intel.feature.node.kubernetes.io/gpu"] = trueValue + ns[nfdGpuWithDriver] = trueValue } if opts.KMMModuleReadyLabel != "" && cp.Spec.KernelModule != nil { @@ -48,6 +53,18 @@ func generateNodeSelector(cp *v1alpha.ClusterPolicy, opts ControllerOpts) map[st return ns } +func generateNodeSelectorDriverless(cp *v1alpha.ClusterPolicy, opts ControllerOpts) map[string]string { + ns := generateNodeSelector(cp, opts) + + // Change the NFD label to driverless, so KMM Pods can be scheduled to the nodes. + if cp.Spec.UseNFDLabeling { + delete(ns, nfdGpuWithDriver) + ns[nfdGpuWithoutDriver] = trueValue + } + + return ns +} + func generateTolerations(cp *v1alpha.ClusterPolicy) []core.Toleration { tolerations := []core.Toleration{} diff --git a/internal/controller/kmm_controller.go b/internal/controller/kmm_controller.go index 8195242..6d9c008 100644 --- a/internal/controller/kmm_controller.go +++ b/internal/controller/kmm_controller.go @@ -161,7 +161,7 @@ func (r *KMMReconciler) setModuleDesiredState(mod *kmmv1beta1.Module, cp *v1alph return fmt.Errorf("failed to set controller reference: %w", err) } - mod.Spec.Selector = generateNodeSelector(cp, r.Opts) + mod.Spec.Selector = generateNodeSelectorDriverless(cp, r.Opts) mod.Spec.Tolerations = generateTolerations(cp) mod.Spec.ImageRepoSecret = cp.Spec.PullSecret diff --git a/internal/controller/kmm_controller_test.go b/internal/controller/kmm_controller_test.go index dee0a5c..582172c 100644 --- a/internal/controller/kmm_controller_test.go +++ b/internal/controller/kmm_controller_test.go @@ -887,6 +887,55 @@ var _ = Describe("KMM Controller", func() { }) }) + Context("KMM nodeSelector generation", func() { + It("should create only requested labels", func() { + cp := &v1alpha.ClusterPolicy{ + Spec: v1alpha.ClusterPolicySpec{ + NodeSelector: map[string]string{ + "foo": "bar", + }, + }, + } + ns := generateNodeSelectorDriverless(cp, ControllerOpts{}) + for k, v := range ns { + switch k { + case "foo": + Expect(v).To(Equal("bar")) + case "kubernetes.io/arch": + Expect(v).To(Equal("amd64")) + default: + Fail("unexpected label in nodeSelector: " + k) + } + Expect(k).NotTo(ContainSubstring("kmm.node.kubernetes.io")) + } + }) + + It("should create only requested labels with NFD rule", func() { + cp := &v1alpha.ClusterPolicy{ + Spec: v1alpha.ClusterPolicySpec{ + NodeSelector: map[string]string{ + "foo123": "bar", + }, + UseNFDLabeling: true, + }, + } + ns := generateNodeSelectorDriverless(cp, ControllerOpts{}) + for k, v := range ns { + switch k { + case "foo123": + Expect(v).To(Equal("bar")) + case "kubernetes.io/arch": + Expect(v).To(Equal("amd64")) + case "intel.feature.node.kubernetes.io/gpu-pci": + Expect(v).To(Equal("true")) + default: + Fail("unexpected label in nodeSelector: " + k) + } + Expect(k).NotTo(ContainSubstring("kmm.node.kubernetes.io")) + } + }) + }) + Context("When toggling container.version between empty and non-empty", func() { newReconciler := func(ns string) *KMMReconciler { return &KMMReconciler{ diff --git a/internal/controller/misc_controller_test.go b/internal/controller/misc_controller_test.go index 15cc006..e0cf549 100644 --- a/internal/controller/misc_controller_test.go +++ b/internal/controller/misc_controller_test.go @@ -166,12 +166,12 @@ var _ = Describe("Misc", func() { nfr := createNfdRule(spec, "") Expect(nfr).NotTo(BeNil()) - Expect(nfr.Spec.Rules).To(HaveLen(9)) + Expect(nfr.Spec.Rules).To(HaveLen(10)) rule := nfr.Spec.Rules[0] checkValues(rule.MatchFeatures[0].MatchExpressions) - b60Rule := nfr.Spec.Rules[2] + b60Rule := nfr.Spec.Rules[3] checkValuesForB60(b60Rule) }) })