Skip to content

Commit 591cd45

Browse files
committed
Add DRA DaemonSet reconciler
Implement a DRAReconciler that manages DRA driver DaemonSets from Module spec.dra, mirroring the DevicePluginReconciler pattern. The reconciler creates DaemonSets with mandatory kubelet-plugins and kubelet-plugins-registry host-path volumes, DRA-specific container names, and a DaemonSetRole label to distinguish DRA DaemonSets. Status patching reports targeted nodes and available pods in status.dra, and clearing status.dra when spec.dra is removed. Also fixes DevicePluginReconciler to exclude DRA-labeled DaemonSets from its device-plugin list, preventing cross-reconciler interference.
1 parent 888c952 commit 591cd45

6 files changed

Lines changed: 1256 additions & 4 deletions

File tree

internal/constants/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
PrivateSignDataKey = "key"
2626

2727
ModuleLoaderRoleLabelValue = "module-loader"
28+
DRARoleLabelValue = "dra"
2829

2930
OperatorNamespaceEnvVar = "OPERATOR_NAMESPACE"
3031
)

internal/controllers/device_plugin_reconciler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ func (dprh *devicePluginReconcilerHelper) getModuleDevicePluginDaemonSets(ctx co
176176
}
177177

178178
devicePluginsList := make([]appsv1.DaemonSet, 0, len(dsList.Items))
179-
// remove the older version module loader daemonsets
180179
for _, ds := range dsList.Items {
181-
if ds.GetLabels()[constants.DaemonSetRole] != constants.ModuleLoaderRoleLabelValue {
180+
role := ds.GetLabels()[constants.DaemonSetRole]
181+
if role != constants.ModuleLoaderRoleLabelValue && role != constants.DRARoleLabelValue {
182182
devicePluginsList = append(devicePluginsList, ds)
183183
}
184184
}

internal/controllers/device_plugin_reconciler_test.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ var _ = Describe("DevicePluginReconciler_getModuleDevicePluginDaemonSets", func(
927927
Expect(dsList).To(BeNil())
928928
})
929929

930-
It("good flow, return only device plugin DSs, either v2 or v1", func() {
930+
It("good flow, return only device plugin DSs, excluding module-loader and DRA roles", func() {
931931
ds1 := appsv1.DaemonSet{
932932
ObjectMeta: metav1.ObjectMeta{
933933
Labels: map[string]string{
@@ -951,9 +951,17 @@ var _ = Describe("DevicePluginReconciler_getModuleDevicePluginDaemonSets", func(
951951
},
952952
},
953953
}
954+
ds4 := appsv1.DaemonSet{
955+
ObjectMeta: metav1.ObjectMeta{
956+
Labels: map[string]string{
957+
constants.ModuleNameLabel: "some name",
958+
constants.DaemonSetRole: constants.DRARoleLabelValue,
959+
},
960+
},
961+
}
954962
clnt.EXPECT().List(ctx, gomock.Any(), gomock.Any()).DoAndReturn(
955963
func(_ interface{}, list *appsv1.DaemonSetList, _ ...interface{}) error {
956-
list.Items = []appsv1.DaemonSet{ds1, ds2, ds3}
964+
list.Items = []appsv1.DaemonSet{ds1, ds2, ds3, ds4}
957965
return nil
958966
},
959967
)
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
Copyright 2022.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
24+
kmmv1beta1 "github.com/kubernetes-sigs/kernel-module-management/api/v1beta1"
25+
"github.com/kubernetes-sigs/kernel-module-management/internal/constants"
26+
"github.com/kubernetes-sigs/kernel-module-management/internal/node"
27+
"github.com/kubernetes-sigs/kernel-module-management/internal/utils"
28+
appsv1 "k8s.io/api/apps/v1"
29+
v1 "k8s.io/api/core/v1"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/runtime"
32+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
35+
"sigs.k8s.io/controller-runtime/pkg/log"
36+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
37+
)
38+
39+
const (
40+
DRAReconcilerName = "DRAReconciler"
41+
42+
kubeletPluginsVolumeName = "kubelet-plugins"
43+
kubeletPluginsPath = "/var/lib/kubelet/plugins/"
44+
kubeletPluginsRegistryVolumeName = "kubelet-plugins-registry"
45+
kubeletPluginsRegistryPath = "/var/lib/kubelet/plugins_registry/"
46+
)
47+
48+
type DRAReconciler struct {
49+
client client.Client
50+
reconHelperAPI draReconcilerHelperAPI
51+
}
52+
53+
func NewDRAReconciler(
54+
client client.Client,
55+
nodeAPI node.Node,
56+
scheme *runtime.Scheme,
57+
) *DRAReconciler {
58+
reconHelperAPI := newDRAReconcilerHelper(client, nodeAPI, scheme)
59+
return &DRAReconciler{
60+
client: client,
61+
reconHelperAPI: reconHelperAPI,
62+
}
63+
}
64+
65+
func (r *DRAReconciler) SetupWithManager(mgr ctrl.Manager) error {
66+
return ctrl.NewControllerManagedBy(mgr).
67+
For(&kmmv1beta1.Module{}).
68+
Owns(&appsv1.DaemonSet{}).
69+
Named(DRAReconcilerName).
70+
Complete(
71+
reconcile.AsReconciler[*kmmv1beta1.Module](r.client, r),
72+
)
73+
}
74+
75+
func (r *DRAReconciler) Reconcile(ctx context.Context, mod *kmmv1beta1.Module) (ctrl.Result, error) {
76+
res := ctrl.Result{}
77+
78+
logger := log.FromContext(ctx)
79+
80+
existingDRADS, err := r.reconHelperAPI.getModuleDRADaemonSets(ctx, mod.Name, mod.Namespace)
81+
if err != nil {
82+
return res, fmt.Errorf("could not get DRA DaemonSets for module %s, namespace %s: %v", mod.Name, mod.Namespace, err)
83+
}
84+
85+
if mod.GetDeletionTimestamp() != nil {
86+
err = r.reconHelperAPI.handleModuleDeletion(ctx, existingDRADS)
87+
return ctrl.Result{}, err
88+
}
89+
90+
if mod.Spec.DRA == nil {
91+
if len(existingDRADS) > 0 {
92+
if err = r.reconHelperAPI.handleModuleDeletion(ctx, existingDRADS); err != nil {
93+
return ctrl.Result{}, err
94+
}
95+
}
96+
return ctrl.Result{}, r.reconHelperAPI.clearDRAStatus(ctx, mod)
97+
}
98+
99+
err = r.reconHelperAPI.handleDRA(ctx, mod, existingDRADS)
100+
if err != nil {
101+
return res, fmt.Errorf("could not handle DRA: %v", err)
102+
}
103+
104+
err = r.reconHelperAPI.moduleUpdateDRAStatus(ctx, mod, existingDRADS)
105+
if err != nil {
106+
return res, fmt.Errorf("failed to update DRA status of the module: %v", err)
107+
}
108+
109+
logger.Info("DRA reconcile loop finished successfully")
110+
111+
return res, nil
112+
}
113+
114+
//go:generate mockgen -source=dra_reconciler.go -package=controllers -destination=mock_dra_reconciler.go draReconcilerHelperAPI,draDaemonSetCreator
115+
116+
type draReconcilerHelperAPI interface {
117+
getModuleDRADaemonSets(ctx context.Context, name, namespace string) ([]appsv1.DaemonSet, error)
118+
handleDRA(ctx context.Context, mod *kmmv1beta1.Module, existingDRADS []appsv1.DaemonSet) error
119+
handleModuleDeletion(ctx context.Context, existingDRADS []appsv1.DaemonSet) error
120+
moduleUpdateDRAStatus(ctx context.Context, mod *kmmv1beta1.Module, existingDRADS []appsv1.DaemonSet) error
121+
clearDRAStatus(ctx context.Context, mod *kmmv1beta1.Module) error
122+
}
123+
124+
type draReconcilerHelper struct {
125+
client client.Client
126+
daemonSetHelper draDaemonSetCreator
127+
nodeAPI node.Node
128+
}
129+
130+
func newDRAReconcilerHelper(client client.Client,
131+
nodeAPI node.Node,
132+
scheme *runtime.Scheme,
133+
) draReconcilerHelperAPI {
134+
daemonSetHelper := newDRADaemonSetCreator(scheme)
135+
return &draReconcilerHelper{
136+
client: client,
137+
daemonSetHelper: daemonSetHelper,
138+
nodeAPI: nodeAPI,
139+
}
140+
}
141+
142+
func (drh *draReconcilerHelper) getModuleDRADaemonSets(ctx context.Context, name, namespace string) ([]appsv1.DaemonSet, error) {
143+
dsList := appsv1.DaemonSetList{}
144+
opts := []client.ListOption{
145+
client.MatchingLabels(map[string]string{
146+
constants.ModuleNameLabel: name,
147+
constants.DaemonSetRole: constants.DRARoleLabelValue,
148+
}),
149+
client.InNamespace(namespace),
150+
}
151+
if err := drh.client.List(ctx, &dsList, opts...); err != nil {
152+
return nil, fmt.Errorf("could not list DaemonSets: %v", err)
153+
}
154+
155+
return dsList.Items, nil
156+
}
157+
158+
func (drh *draReconcilerHelper) handleDRA(ctx context.Context, mod *kmmv1beta1.Module, existingDRADS []appsv1.DaemonSet) error {
159+
if mod.Spec.DRA == nil {
160+
return nil
161+
}
162+
163+
logger := log.FromContext(ctx)
164+
165+
var ds *appsv1.DaemonSet
166+
if len(existingDRADS) > 0 {
167+
ds = &existingDRADS[0]
168+
} else {
169+
logger.Info("creating new DRA DaemonSet")
170+
ds = &appsv1.DaemonSet{
171+
ObjectMeta: metav1.ObjectMeta{Namespace: mod.Namespace, GenerateName: mod.Name + "-dra-"},
172+
}
173+
}
174+
175+
opRes, err := controllerutil.CreateOrPatch(ctx, drh.client, ds, func() error {
176+
return drh.daemonSetHelper.setDRAAsDesired(ctx, ds, mod)
177+
})
178+
179+
if err == nil {
180+
logger.Info("Reconciled DRA", "name", ds.Name, "result", opRes)
181+
}
182+
183+
return err
184+
}
185+
186+
func (drh *draReconcilerHelper) handleModuleDeletion(ctx context.Context, existingDRADS []appsv1.DaemonSet) error {
187+
for _, ds := range existingDRADS {
188+
err := drh.client.Delete(ctx, &ds)
189+
if err != nil {
190+
return fmt.Errorf("failed to delete DRA DaemonSet %s/%s: %v", ds.Namespace, ds.Name, err)
191+
}
192+
}
193+
return nil
194+
}
195+
196+
func (drh *draReconcilerHelper) moduleUpdateDRAStatus(ctx context.Context,
197+
mod *kmmv1beta1.Module,
198+
existingDRADS []appsv1.DaemonSet) error {
199+
200+
if mod.Spec.DRA == nil {
201+
return nil
202+
}
203+
204+
numTargetedNodes, err := drh.nodeAPI.GetNumTargetedNodes(ctx, mod.Spec.Selector, mod.Spec.Tolerations)
205+
if err != nil {
206+
return fmt.Errorf("failed to determine the number of nodes targeted by Module %s/%s selector: %v", mod.Namespace, mod.Name, err)
207+
}
208+
209+
numAvailable := int32(0)
210+
for _, ds := range existingDRADS {
211+
numAvailable += ds.Status.NumberAvailable
212+
}
213+
214+
unmodifiedMod := mod.DeepCopy()
215+
216+
mod.Status.DRA.NodesMatchingSelectorNumber = int32(numTargetedNodes)
217+
mod.Status.DRA.DesiredNumber = int32(numTargetedNodes)
218+
mod.Status.DRA.AvailableNumber = numAvailable
219+
220+
return drh.client.Status().Patch(ctx, mod, client.MergeFrom(unmodifiedMod))
221+
}
222+
223+
func (drh *draReconcilerHelper) clearDRAStatus(ctx context.Context, mod *kmmv1beta1.Module) error {
224+
emptyStatus := kmmv1beta1.DaemonSetStatus{}
225+
if mod.Status.DRA == emptyStatus {
226+
return nil
227+
}
228+
229+
unmodifiedMod := mod.DeepCopy()
230+
231+
mod.Status.DRA = kmmv1beta1.DaemonSetStatus{}
232+
233+
return drh.client.Status().Patch(ctx, mod, client.MergeFrom(unmodifiedMod))
234+
}
235+
236+
type draDaemonSetCreator interface {
237+
setDRAAsDesired(ctx context.Context, ds *appsv1.DaemonSet, mod *kmmv1beta1.Module) error
238+
}
239+
240+
type draDaemonSetCreatorImpl struct {
241+
scheme *runtime.Scheme
242+
}
243+
244+
func newDRADaemonSetCreator(scheme *runtime.Scheme) draDaemonSetCreator {
245+
return &draDaemonSetCreatorImpl{
246+
scheme: scheme,
247+
}
248+
}
249+
250+
func (dsci *draDaemonSetCreatorImpl) setDRAAsDesired(
251+
ctx context.Context,
252+
ds *appsv1.DaemonSet,
253+
mod *kmmv1beta1.Module,
254+
) error {
255+
if ds == nil {
256+
return errors.New("ds cannot be nil")
257+
}
258+
259+
if mod.Spec.DRA == nil {
260+
return errors.New("DRA spec in module should not be nil")
261+
}
262+
263+
hostPathDirOrCreate := v1.HostPathDirectoryOrCreate
264+
hostPathDir := v1.HostPathDirectory
265+
266+
pluginsVolume := v1.Volume{
267+
Name: kubeletPluginsVolumeName,
268+
VolumeSource: v1.VolumeSource{
269+
HostPath: &v1.HostPathVolumeSource{
270+
Path: kubeletPluginsPath,
271+
Type: &hostPathDirOrCreate,
272+
},
273+
},
274+
}
275+
276+
registryVolume := v1.Volume{
277+
Name: kubeletPluginsRegistryVolumeName,
278+
VolumeSource: v1.VolumeSource{
279+
HostPath: &v1.HostPathVolumeSource{
280+
Path: kubeletPluginsRegistryPath,
281+
Type: &hostPathDir,
282+
},
283+
},
284+
}
285+
286+
containerVolumeMounts := []v1.VolumeMount{
287+
{Name: kubeletPluginsVolumeName, MountPath: kubeletPluginsPath},
288+
{Name: kubeletPluginsRegistryVolumeName, MountPath: kubeletPluginsRegistryPath},
289+
}
290+
291+
standardLabels := map[string]string{
292+
constants.ModuleNameLabel: mod.Name,
293+
constants.DaemonSetRole: constants.DRARoleLabelValue,
294+
}
295+
296+
nodeSelector := map[string]string{
297+
utils.GetKernelModuleReadyNodeLabel(mod.Namespace, mod.Name): "",
298+
}
299+
300+
ds.SetLabels(
301+
overrideLabels(ds.GetLabels(), standardLabels),
302+
)
303+
304+
ds.Spec = appsv1.DaemonSetSpec{
305+
Selector: &metav1.LabelSelector{MatchLabels: standardLabels},
306+
Template: v1.PodTemplateSpec{
307+
ObjectMeta: metav1.ObjectMeta{
308+
Labels: standardLabels,
309+
Finalizers: []string{constants.NodeLabelerFinalizer},
310+
},
311+
Spec: v1.PodSpec{
312+
InitContainers: generatePodContainerSpec(mod.Spec.DRA.InitContainer, "dra-init", nil),
313+
Containers: generatePodContainerSpec(&mod.Spec.DRA.Container, "dra", containerVolumeMounts),
314+
PriorityClassName: "system-node-critical",
315+
ImagePullSecrets: getPodPullSecrets(mod.Spec.ImageRepoSecret),
316+
NodeSelector: nodeSelector,
317+
ServiceAccountName: mod.Spec.DRA.ServiceAccountName,
318+
Volumes: append([]v1.Volume{pluginsVolume, registryVolume}, mod.Spec.DRA.Volumes...),
319+
Tolerations: mod.Spec.Tolerations,
320+
AutomountServiceAccountToken: mod.Spec.DRA.AutomountServiceAccountToken,
321+
},
322+
},
323+
}
324+
325+
return controllerutil.SetControllerReference(mod, ds, dsci.scheme)
326+
}

0 commit comments

Comments
 (0)