Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ COPY --from=builder /go/src/github.com/openshift-kni/numaresources-operator/bin/
COPY --from=builder /go/src/github.com/openshift-kni/numaresources-operator/bin/buildinfo.json /usr/local/share
RUN mkdir /etc/resource-topology-exporter/ && \
touch /etc/resource-topology-exporter/config.yaml
RUN microdnf install -y hwdata && \
RUN microdnf install -y --disablerepo='rhel-*' hwdata && \
microdnf clean -y all
USER 65532:65532
ENTRYPOINT ["/bin/numaresources-operator"]
20 changes: 15 additions & 5 deletions internal/controller/numaresourcesoperator_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,22 +522,32 @@ func (r *NUMAResourcesOperatorReconciler) syncNUMAResourcesOperatorResources(ctx
dsPoolPairs := []poolDaemonSet{}

// using a slice of poolDaemonSet instead of a map because Go maps assignment order is not consistent and non-deterministic
err = rteupdate.DaemonSetUserImageSettings(r.RTEManifests.Core.DaemonSet, instance.Spec.ExporterImage, r.Images.Preferred(), r.ImagePullPolicy)
// mutatedManifests are rendered manifests which their base derives from the existing resources
// found on the cluster.
// those manifests were mutated by the API server and other controllers running on the cluster, hence the name.
// this way it minimizes the diffs between the desired and existing state only to the fields we care about.
// in case the manifest/resource was not found on the cluster (should be created)
// it uses the local stored manifest.
mutatedManifests, err := rtestate.MutatedFromExisting(existing, r.RTEManifests, r.Namespace)
if err != nil {
return dsPoolPairs, err
}

err = rteupdate.DaemonSetPauseContainerSettings(r.RTEManifests.Core.DaemonSet)
err = rteupdate.DaemonSetUserImageSettings(mutatedManifests.Core.DaemonSet, instance.Spec.ExporterImage, r.Images.Preferred(), r.ImagePullPolicy)
if err != nil {
return dsPoolPairs, err
}

err = loglevel.UpdatePodSpec(&r.RTEManifests.Core.DaemonSet.Spec.Template.Spec, manifests.ContainerNameRTE, instance.Spec.LogLevel)
err = rteupdate.DaemonSetPauseContainerSettings(mutatedManifests.Core.DaemonSet)
if err != nil {
return dsPoolPairs, err
}

rteupdate.SecurityContextConstraint(r.RTEManifests.Core.SecurityContextConstraint, true) // force to legacy context
err = loglevel.UpdatePodSpec(&mutatedManifests.Core.DaemonSet.Spec.Template.Spec, manifests.ContainerNameRTE, instance.Spec.LogLevel)
if err != nil {
return dsPoolPairs, err
}
rteupdate.SecurityContextConstraint(mutatedManifests.Core.SecurityContextConstraint, true) // force to legacy context
// SCC v2 needs no updates

existing = existing.WithManifestsUpdater(func(poolName string, gdm *rtestate.GeneratedDesiredManifest) error {
Expand All @@ -549,7 +559,7 @@ func (r *NUMAResourcesOperatorReconciler) syncNUMAResourcesOperatorResources(ctx
return nil
})

for _, objState := range existing.State(r.RTEManifests) {
for _, objState := range existing.State(mutatedManifests) {
if objState.Error != nil {
// We are likely in the bootstrap scenario. In this case, which is expected once, everything is fine.
// If it happens past bootstrap, still carry on. We know what to do, and we do want to enforce the desired state.
Expand Down
1 change: 1 addition & 0 deletions pkg/apply/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func ApplyObject(ctx context.Context, cli k8sclient.Client, objState objectstate
updated := false
if !ok {
klog.InfoS("updating", "object", objDesc)
recordApplyClientUpdate()
if err := cli.Update(ctx, merged); err != nil {
return nil, updated, fmt.Errorf("could not update object %s: %w", objDesc, err)
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/apply/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apply

import (
"github.com/prometheus/client_golang/prometheus"

crmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
)

// MetricApplyClientUpdatesTotal is the Prometheus metric name for applyClientUpdatesTotal.
const MetricApplyClientUpdatesTotal = "numaresources_operator_apply_client_updates_total"

var applyClientUpdatesTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "numaresources_operator",
Name: "apply_client_updates_total",
Help: "Number of client Update calls issued from apply.ApplyObject. Incremented for every Update call.",
},
)

func init() {
crmetrics.Registry.MustRegister(applyClientUpdatesTotal)
}

func recordApplyClientUpdate() {
applyClientUpdatesTotal.Inc()
}
20 changes: 10 additions & 10 deletions pkg/numaresourcesscheduler/objectstate/sched/sched.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/openshift-kni/numaresources-operator/pkg/objectstate"
"github.com/openshift-kni/numaresources-operator/pkg/objectstate/compare"
"github.com/openshift-kni/numaresources-operator/pkg/objectstate/merge"
"github.com/openshift-kni/numaresources-operator/pkg/objectupdate/volume"
)

const (
Expand Down Expand Up @@ -224,14 +225,13 @@ func SchedulerNameFromObject(obj client.Object) (string, bool) {
}

func NewSchedConfigVolume(schedVolumeConfigName, configMapName string) corev1.Volume {
return corev1.Volume{
Name: schedVolumeConfigName,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMapName,
},
},
},
}
// Create a temporary pod spec and container to use the volume package
podSpec := &corev1.PodSpec{}
container := &corev1.Container{}

// Use the volume package to create the ConfigMap volume
volume.AddConfigMap(podSpec, container, schedVolumeConfigName, "/tmp", configMapName, volume.DefaultMode, false, false)

// Return just the volume part (we don't need the mount)
return podSpec.Volumes[0]
}
9 changes: 8 additions & 1 deletion pkg/objectstate/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,18 @@ type ExistingManifests struct {
}

func (em *ExistingManifests) State(mf apimanifests.Manifests) []objectstate.ObjectState {
desired := mf.Crd.DeepCopy()
// Use the existing object as a base when available so we preserve
// defaults applied by the API server (e.g. Conversion strategy)
o := objectstate.ObjectState{Error: em.CrdError}
if !o.IsNotFoundError() && em.Existing.Crd != nil {
desired.Spec.Conversion = em.Existing.Crd.Spec.Conversion
}
return []objectstate.ObjectState{
{
Existing: em.Existing.Crd,
Error: em.CrdError,
Desired: mf.Crd.DeepCopy(),
Desired: desired,
Compare: compare.Object,
Merge: merge.ObjectForUpdate,
},
Expand Down
105 changes: 105 additions & 0 deletions pkg/objectstate/rte/rte.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package rte

import (
"context"
"time"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -34,6 +35,7 @@ import (
"github.com/k8stopologyawareschedwg/deployer/pkg/deployer/platform"
rtemanifests "github.com/k8stopologyawareschedwg/deployer/pkg/manifests/rte"
k8swgrteupdate "github.com/k8stopologyawareschedwg/deployer/pkg/objectupdate/rte"
"github.com/k8stopologyawareschedwg/deployer/pkg/options"

nropv1 "github.com/openshift-kni/numaresources-operator/api/v1"
nodegroupv1 "github.com/openshift-kni/numaresources-operator/api/v1/helper/nodegroup"
Expand Down Expand Up @@ -169,6 +171,101 @@ func SkipManifestUpdate(mcpName string, gdm *GeneratedDesiredManifest) error {
return nil
}

func MutatedFromExisting(existingMF *ExistingManifests, defaultManifests Manifests, namespace string) (Manifests, error) {
mf := Manifests{
Core: existingMF.existing.Core.Clone(),
Metrics: existingMF.existing.Metrics.Clone(),
}

o := objectstate.ObjectState{Error: existingMF.errs.Core.ClusterRole}
if o.IsNotFoundError() {
mf.Core.ClusterRole = defaultManifests.Core.ClusterRole.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.ClusterRoleBinding}
if o.IsNotFoundError() {
mf.Core.ClusterRoleBinding = defaultManifests.Core.ClusterRoleBinding.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.Role}
if o.IsNotFoundError() {
mf.Core.Role = defaultManifests.Core.Role.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.RoleBinding}
if o.IsNotFoundError() {
mf.Core.RoleBinding = defaultManifests.Core.RoleBinding.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.ServiceAccount}
if o.IsNotFoundError() {
mf.Core.ServiceAccount = defaultManifests.Core.ServiceAccount.DeepCopy()
}

if defaultManifests.Core.SecurityContextConstraint != nil {
o = objectstate.ObjectState{Error: existingMF.errs.Core.SCC}
if o.IsNotFoundError() {
mf.Core.SecurityContextConstraint = defaultManifests.Core.SecurityContextConstraint.DeepCopy()
}
}

if defaultManifests.Core.SecurityContextConstraintV2 != nil {
o = objectstate.ObjectState{Error: existingMF.errs.Core.SCCv2}
if o.IsNotFoundError() {
mf.Core.SecurityContextConstraintV2 = defaultManifests.Core.SecurityContextConstraintV2.DeepCopy()
}
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.APIServerNetworkPolicy}
if o.IsNotFoundError() {
mf.Core.APIServerNetworkPolicy = defaultManifests.Core.APIServerNetworkPolicy.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.MetricsServerNetworkPolicy}
if o.IsNotFoundError() {
mf.Core.MetricsServerNetworkPolicy = defaultManifests.Core.MetricsServerNetworkPolicy.DeepCopy()
}

o = objectstate.ObjectState{Error: existingMF.errs.Core.DefaultNetworkPolicy}
if o.IsNotFoundError() {
mf.Core.DefaultNetworkPolicy = defaultManifests.Core.DefaultNetworkPolicy.DeepCopy()
}

// there are multiple resources of DaemonSets
// (one per nodeGroup), so we should use a default manifest + existing/mutated spec,
// and the rest will be updated later
mf.Core.DaemonSet = defaultManifests.Core.DaemonSet.DeepCopy()
for _, ds := range existingMF.daemonSets {
if ds.daemonSetError == nil {
// use the spec from one of the existing so we won't end up with
// diffs that derives from default values applied by the API server
mf.Core.DaemonSet.Spec = *ds.daemonSet.Spec.DeepCopy()
}
}
Comment thread
Tal-or marked this conversation as resolved.
// Clear volumes and volume mounts before rendering to avoid duplicates
// The Render() function will add them back based on the configuration
clearVolumesAndVolumeMounts(mf.Core.DaemonSet)

o = objectstate.ObjectState{Error: existingMF.errs.Metrics.Service}
if o.IsNotFoundError() {
mf.Metrics.Service = defaultManifests.Metrics.Service.DeepCopy()
}

var err error
mf.Core, err = mf.Core.Render(options.UpdaterDaemon{
Namespace: namespace,
DaemonSet: options.DaemonSet{
Verbose: 2,
NotificationEnable: true,
UpdateInterval: 10 * time.Second,
},
})
if err != nil {
return mf, err
}
return mf, err
}

func (em *ExistingManifests) State(mf Manifests) []objectstate.ObjectState {
ret := []objectstate.ObjectState{
{
Expand Down Expand Up @@ -372,3 +469,11 @@ func getObject(ctx context.Context, cli client.Client, key client.ObjectKey, obj
*err = cli.Get(ctx, key, obj)
return *err == nil
}

func clearVolumesAndVolumeMounts(ds *appsv1.DaemonSet) {
podSpec := &ds.Spec.Template.Spec
podSpec.Volumes = nil
for i := range podSpec.Containers {
podSpec.Containers[i].VolumeMounts = nil
}
}
Loading
Loading