diff --git a/api/v1alpha1/features.go b/api/v1alpha1/features.go index 0bf375073..a02e254ae 100644 --- a/api/v1alpha1/features.go +++ b/api/v1alpha1/features.go @@ -46,6 +46,16 @@ type Features struct { // +optional NamespaceScopeEnabled *bool `json:"nssEnabled,omitempty"` + // Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed + // real node capacity. Defaults to true. + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Node CPU Capping Enabled",xDescriptors="urn:alm:descriptor:com.tectonic.ui:booleanSwitch" + // +optional + NodeCpuCappingEnabled *bool `json:"nodeCpuCappingEnabled,omitempty"` + + // Enables the bearer-token / Kubernetes RBAC authentication path on the operand (TokenReview + SubjectAccessReview). Defaults to true. + // +optional + KubeRBACAuthEnabled *bool `json:"kubeRBACAuthEnabled,omitempty"` + // Special terms, must be granted by IBM Pricing. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Custom namespaces Config Map",xDescriptors="urn:alm:descriptor:com.tectonic.ui:hidden" // +optional @@ -65,6 +75,13 @@ func (spec *IBMLicensingSpec) IsNamespaceScopeEnabled() bool { return spec.HaveFeatures() && spec.Features.NamespaceScopeEnabled != nil && *spec.Features.NamespaceScopeEnabled } +func (spec *IBMLicensingSpec) IsKubeRBACAuthEnabled() bool { + if !spec.HaveFeatures() || spec.Features.KubeRBACAuthEnabled == nil { + return true + } + return *spec.Features.KubeRBACAuthEnabled +} + func (spec *IBMLicensingSpec) IsCustomNamespaceScopeConfigMap() bool { return spec.HaveFeatures() && spec.Features != nil && spec.Features.CustomNamespaceScopeConfigMap != nil } diff --git a/api/v1alpha1/helper.go b/api/v1alpha1/helper.go index 838c234b7..e390c10c1 100644 --- a/api/v1alpha1/helper.go +++ b/api/v1alpha1/helper.go @@ -194,8 +194,10 @@ func (spec *IBMLicensingSpec) IsChargebackEnabled() bool { } func (spec *IBMLicensingSpec) IsNodeCpuCappingEnabled() bool { - // Default true: nil treated as enabled. - return spec.NodeCpuCappingEnabled == nil || *spec.NodeCpuCappingEnabled + if !spec.HaveFeatures() || spec.Features.NodeCpuCappingEnabled == nil { + return true + } + return *spec.Features.NodeCpuCappingEnabled } func (container *Container) initResourcesIfNil() { diff --git a/api/v1alpha1/helper_test.go b/api/v1alpha1/helper_test.go index 72293d273..6b862c149 100644 --- a/api/v1alpha1/helper_test.go +++ b/api/v1alpha1/helper_test.go @@ -29,13 +29,13 @@ func TestIsNodeCpuCappingEnabledNil(t *testing.T) { } func TestIsNodeCpuCappingEnabledExplicitTrue(t *testing.T) { - spec := &IBMLicensingSpec{NodeCpuCappingEnabled: new(true)} + spec := &IBMLicensingSpec{Features: &Features{NodeCpuCappingEnabled: new(true)}} assert.True(t, spec.IsNodeCpuCappingEnabled(), "NodeCpuCappingEnabled=true should return true.") } func TestIsNodeCpuCappingEnabledExplicitFalse(t *testing.T) { - spec := &IBMLicensingSpec{NodeCpuCappingEnabled: new(false)} + spec := &IBMLicensingSpec{Features: &Features{NodeCpuCappingEnabled: new(false)}} assert.False(t, spec.IsNodeCpuCappingEnabled(), "NodeCpuCappingEnabled=false should return false.") } diff --git a/api/v1alpha1/ibmlicensing_types.go b/api/v1alpha1/ibmlicensing_types.go index 335f05441..d4caf60d5 100644 --- a/api/v1alpha1/ibmlicensing_types.go +++ b/api/v1alpha1/ibmlicensing_types.go @@ -115,11 +115,6 @@ type IBMLicensingSpec struct { // +optional ChargebackEnabled *bool `json:"chargebackEnabled,omitempty"` - // Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed - // real node capacity. Defaults to true. - // +optional - NodeCpuCappingEnabled *bool `json:"nodeCpuCappingEnabled,omitempty"` - // Chargeback data retention period in days. Default value is 62 days. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Chargeback Retention Period in days",xDescriptors="urn:alm:descriptor:com.tectonic.ui:number" // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index bb4484fbe..97c1c7138 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -86,6 +86,16 @@ func (in *Features) DeepCopyInto(out *Features) { *out = new(bool) **out = **in } + if in.NodeCpuCappingEnabled != nil { + in, out := &in.NodeCpuCappingEnabled, &out.NodeCpuCappingEnabled + *out = new(bool) + **out = **in + } + if in.KubeRBACAuthEnabled != nil { + in, out := &in.KubeRBACAuthEnabled, &out.KubeRBACAuthEnabled + *out = new(bool) + **out = **in + } if in.CustomNamespaceScopeConfigMap != nil { in, out := &in.CustomNamespaceScopeConfigMap, &out.CustomNamespaceScopeConfigMap *out = new(string) @@ -455,11 +465,6 @@ func (in *IBMLicensingSpec) DeepCopyInto(out *IBMLicensingSpec) { *out = new(bool) **out = **in } - if in.NodeCpuCappingEnabled != nil { - in, out := &in.NodeCpuCappingEnabled, &out.NodeCpuCappingEnabled - *out = new(bool) - **out = **in - } if in.ChargebackRetentionPeriod != nil { in, out := &in.ChargebackRetentionPeriod, &out.ChargebackRetentionPeriod *out = new(int) diff --git a/bundle/manifests/ibm-licensing-operator.clusterserviceversion.yaml b/bundle/manifests/ibm-licensing-operator.clusterserviceversion.yaml index 5e159ef23..0d2e4dde9 100644 --- a/bundle/manifests/ibm-licensing-operator.clusterserviceversion.yaml +++ b/bundle/manifests/ibm-licensing-operator.clusterserviceversion.yaml @@ -81,7 +81,7 @@ metadata: categories: Monitoring certified: "false" containerImage: icr.io/cpopen/ibm-licensing-operator:4.2.23 - createdAt: "2026-05-15T12:42:13Z" + createdAt: "2026-06-08T11:56:23Z" description: The IBM Licensing Operator provides a Kubernetes CRD-Based API to monitor the license usage of products. features.operators.openshift.io/disconnected: "true" features.operators.openshift.io/fips-compliant: "true" @@ -224,6 +224,13 @@ spec: path: features.hyperThreading x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed + real node capacity. Defaults to true. + displayName: Node CPU Capping Enabled + path: features.nodeCpuCappingEnabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Special terms, must be granted by IBM Pricing. displayName: Custom namespaces Config Map path: features.nssConfigMap diff --git a/bundle/manifests/operator.ibm.com_ibmlicensings.yaml b/bundle/manifests/operator.ibm.com_ibmlicensings.yaml index 86994d9a0..a182e2e72 100644 --- a/bundle/manifests/operator.ibm.com_ibmlicensings.yaml +++ b/bundle/manifests/operator.ibm.com_ibmlicensings.yaml @@ -117,6 +117,16 @@ spec: required: - threadsPerCore type: object + kubeRBACAuthEnabled: + description: Enables the bearer-token / Kubernetes RBAC authentication + path on the operand (TokenReview + SubjectAccessReview). Defaults + to true. + type: boolean + nodeCpuCappingEnabled: + description: |- + Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed + real node capacity. Defaults to true. + type: boolean nssConfigMap: description: Special terms, must be granted by IBM Pricing. type: string @@ -245,11 +255,6 @@ spec: - INFO - VERBOSE type: string - nodeCpuCappingEnabled: - description: |- - Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed - real node capacity. Defaults to true. - type: boolean resources: description: ResourceRequirements describes the compute resource requirements. properties: diff --git a/config/crd/bases/operator.ibm.com_ibmlicensings.yaml b/config/crd/bases/operator.ibm.com_ibmlicensings.yaml index c4929f227..d58432b2e 100644 --- a/config/crd/bases/operator.ibm.com_ibmlicensings.yaml +++ b/config/crd/bases/operator.ibm.com_ibmlicensings.yaml @@ -113,6 +113,16 @@ spec: required: - threadsPerCore type: object + kubeRBACAuthEnabled: + description: Enables the bearer-token / Kubernetes RBAC authentication + path on the operand (TokenReview + SubjectAccessReview). Defaults + to true. + type: boolean + nodeCpuCappingEnabled: + description: |- + Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed + real node capacity. Defaults to true. + type: boolean nssConfigMap: description: Special terms, must be granted by IBM Pricing. type: string @@ -241,11 +251,6 @@ spec: - INFO - VERBOSE type: string - nodeCpuCappingEnabled: - description: |- - Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed - real node capacity. Defaults to true. - type: boolean resources: description: ResourceRequirements describes the compute resource requirements. properties: diff --git a/config/manifests/bases/ibm-licensing-operator.clusterserviceversion.yaml b/config/manifests/bases/ibm-licensing-operator.clusterserviceversion.yaml index e748e2ad7..779a4657b 100644 --- a/config/manifests/bases/ibm-licensing-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/ibm-licensing-operator.clusterserviceversion.yaml @@ -179,6 +179,13 @@ spec: path: features.hyperThreading x-descriptors: - urn:alm:descriptor:com.tectonic.ui:hidden + - description: |- + Enables node CPU capping. When false, the operand will skip calls to the Kubernetes node API; node-capping is not applied and metrics may exceed + real node capacity. Defaults to true. + displayName: Node CPU Capping Enabled + path: features.nodeCpuCappingEnabled + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: Special terms, must be granted by IBM Pricing. displayName: Custom namespaces Config Map path: features.nssConfigMap diff --git a/controllers/resources/service/containers.go b/controllers/resources/service/containers.go index 2312c8d41..3c8c54792 100644 --- a/controllers/resources/service/containers.go +++ b/controllers/resources/service/containers.go @@ -52,10 +52,6 @@ func getLicensingEnvironmentVariables(spec operatorv1alpha1.IBMLicensingSpec) [] Name: "ENABLE_INSTANA_METRIC_COLLECTION", Value: strconv.FormatBool(spec.EnableInstanaMetricCollection), }, - { - Name: "NODE_CPU_CAPPING_ENABLED", - Value: strconv.FormatBool(spec.IsNodeCpuCappingEnabled()), - }, } if spec.IsDebug() { environmentVariables = append(environmentVariables, corev1.EnvVar{ @@ -106,6 +102,18 @@ func getLicensingEnvironmentVariables(spec operatorv1alpha1.IBMLicensingSpec) [] Value: strconv.Itoa(*htThreadsPerCores), }) } + if !spec.IsNodeCpuCappingEnabled() { + environmentVariables = append(environmentVariables, corev1.EnvVar{ + Name: "NODE_CPU_CAPPING_ENABLED", + Value: "false", + }) + } + if !spec.IsKubeRBACAuthEnabled() { + environmentVariables = append(environmentVariables, corev1.EnvVar{ + Name: "KUBE_RBAC_AUTH_ENABLED", + Value: "false", + }) + } if spec.IsNamespaceScopeEnabled() { environmentVariables = append(environmentVariables, corev1.EnvVar{ Name: "NAMESPACE_SCOPE_ENABLED", diff --git a/controllers/resources/service/containers_test.go b/controllers/resources/service/containers_test.go index 10ede56f1..c61a361cd 100644 --- a/controllers/resources/service/containers_test.go +++ b/controllers/resources/service/containers_test.go @@ -196,9 +196,9 @@ func TestGetLicensingEnvironmentVariablesNodeCpuCappingDefault(t *testing.T) { func TestGetLicensingEnvironmentVariablesNodeCpuCappingExplicitTrue(t *testing.T) { spec := operatorv1alpha1.IBMLicensingSpec{ - InstanceNamespace: "namespace", - Datasource: "datacollector", - NodeCpuCappingEnabled: ptr.To(true), + InstanceNamespace: "namespace", + Datasource: "datacollector", + Features: &operatorv1alpha1.Features{NodeCpuCappingEnabled: ptr.To(true)}, } envVars := getLicensingEnvironmentVariables(spec) @@ -208,9 +208,9 @@ func TestGetLicensingEnvironmentVariablesNodeCpuCappingExplicitTrue(t *testing.T func TestGetLicensingEnvironmentVariablesNodeCpuCappingExplicitFalse(t *testing.T) { spec := operatorv1alpha1.IBMLicensingSpec{ - InstanceNamespace: "namespace", - Datasource: "datacollector", - NodeCpuCappingEnabled: ptr.To(false), + InstanceNamespace: "namespace", + Datasource: "datacollector", + Features: &operatorv1alpha1.Features{NodeCpuCappingEnabled: ptr.To(false)}, } envVars := getLicensingEnvironmentVariables(spec) @@ -218,6 +218,57 @@ func TestGetLicensingEnvironmentVariablesNodeCpuCappingExplicitFalse(t *testing. "NodeCpuCappingEnabled=false, NODE_CPU_CAPPING_ENABLED=false should be added to Licensing pod.") } +func TestGetLicensingEnvironmentVariablesKubeRBACAuthEnabledFeatureNil(t *testing.T) { + spec := operatorv1alpha1.IBMLicensingSpec{ + InstanceNamespace: "namespace", + Datasource: "datacollector", + } + + envVars := getLicensingEnvironmentVariables(spec) + assert.Contains(t, envVars, corev1.EnvVar{Name: "KUBE_RBAC_AUTH_ENABLED", Value: "true"}, + "Features is nil, KUBE_RBAC_AUTH_ENABLED=true should be added to Licensing pod.") +} + +func TestGetLicensingEnvironmentVariablesKubeRBACAuthEnabledPointerNil(t *testing.T) { + spec := operatorv1alpha1.IBMLicensingSpec{ + InstanceNamespace: "namespace", + Datasource: "datacollector", + Features: &operatorv1alpha1.Features{}, + } + + envVars := getLicensingEnvironmentVariables(spec) + assert.Contains(t, envVars, corev1.EnvVar{Name: "KUBE_RBAC_AUTH_ENABLED", Value: "true"}, + "KubeRBACAuthEnabled pointer is nil, KUBE_RBAC_AUTH_ENABLED=true should be added to Licensing pod.") +} + +func TestGetLicensingEnvironmentVariablesKubeRBACAuthEnabledExplicitTrue(t *testing.T) { + spec := operatorv1alpha1.IBMLicensingSpec{ + InstanceNamespace: "namespace", + Datasource: "datacollector", + Features: &operatorv1alpha1.Features{ + KubeRBACAuthEnabled: ptr.To(true), + }, + } + + envVars := getLicensingEnvironmentVariables(spec) + assert.Contains(t, envVars, corev1.EnvVar{Name: "KUBE_RBAC_AUTH_ENABLED", Value: "true"}, + "KubeRBACAuthEnabled=true, KUBE_RBAC_AUTH_ENABLED=true should be added to Licensing pod.") +} + +func TestGetLicensingEnvironmentVariablesKubeRBACAuthEnabledExplicitFalse(t *testing.T) { + spec := operatorv1alpha1.IBMLicensingSpec{ + InstanceNamespace: "namespace", + Datasource: "datacollector", + Features: &operatorv1alpha1.Features{ + KubeRBACAuthEnabled: ptr.To(false), + }, + } + + envVars := getLicensingEnvironmentVariables(spec) + assert.Contains(t, envVars, corev1.EnvVar{Name: "KUBE_RBAC_AUTH_ENABLED", Value: "false"}, + "KubeRBACAuthEnabled=false, KUBE_RBAC_AUTH_ENABLED=false should be added to Licensing pod.") +} + func Contains[T comparable](s []T, e T) bool { for _, v := range s { if v == e {