@@ -20,27 +20,58 @@ import (
2020 "context"
2121 "errors"
2222 "fmt"
23+ "os"
24+ "strings"
25+ "sync"
26+ "time"
2327
28+ "google.golang.org/grpc"
29+ corev1 "k8s.io/api/core/v1"
2430 resourceapi "k8s.io/api/resource/v1"
31+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2532 "k8s.io/apimachinery/pkg/types"
2633 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2734 coreclientset "k8s.io/client-go/kubernetes"
2835 "k8s.io/dynamic-resource-allocation/kubeletplugin"
2936 "k8s.io/klog/v2"
37+ drahealthv1alpha1 "k8s.io/kubelet/pkg/apis/dra-health/v1alpha1"
3038)
3139
40+ type DeviceHealthStatus struct {
41+ Health drahealthv1alpha1.HealthStatus
42+ Message string
43+ }
44+
3245type driver struct {
46+ drahealthv1alpha1.UnimplementedDRAResourceHealthServer
47+
3348 client coreclientset.Interface
3449 helper * kubeletplugin.Helper
3550 state * DeviceState
3651 healthcheck * healthcheck
3752 cancelCtx func (error )
53+
54+ config * Config
55+ poolName string
56+ simulator * HealthSimulator
57+ healthMu sync.RWMutex
58+ deviceHealth map [string ]* DeviceHealthStatus
59+ healthClients []chan * drahealthv1alpha1.NodeWatchResourcesResponse
60+ clientsMu sync.RWMutex
61+ healthOverrides map [string ]bool
62+ stopHealthCh chan struct {}
63+ healthWg sync.WaitGroup
3864}
3965
4066func NewDriver (ctx context.Context , config * Config ) (* driver , error ) {
4167 driver := & driver {
42- client : config .coreclient ,
43- cancelCtx : config .cancelMainCtx ,
68+ client : config .coreclient ,
69+ cancelCtx : config .cancelMainCtx ,
70+ config : config ,
71+ poolName : config .flags .nodeName ,
72+ deviceHealth : make (map [string ]* DeviceHealthStatus ),
73+ healthOverrides : make (map [string ]bool ),
74+ stopHealthCh : make (chan struct {}),
4475 }
4576
4677 state , err := NewDeviceState (config )
@@ -49,6 +80,18 @@ func NewDriver(ctx context.Context, config *Config) (*driver, error) {
4980 }
5081 driver .state = state
5182
83+ var deviceNames []string
84+ for deviceName := range state .allocatable {
85+ deviceNames = append (deviceNames , deviceName )
86+ driver .deviceHealth [deviceName ] = & DeviceHealthStatus {
87+ Health : drahealthv1alpha1 .HealthStatus_HEALTHY ,
88+ Message : fmt .Sprintf ("Device %s initialized successfully" , deviceName ),
89+ }
90+ }
91+
92+ driver .simulator = NewHealthSimulator (deviceNames )
93+ klog .Infof ("Device health reporting enabled for %d devices" , len (deviceNames ))
94+
5295 helper , err := kubeletplugin .Start (ctx , driver ,
5396 kubeletplugin .KubeClient (config .coreclient ),
5497 kubeletplugin .NodeName (config .flags .nodeName ),
@@ -71,13 +114,30 @@ func NewDriver(ctx context.Context, config *Config) (*driver, error) {
71114 return nil , err
72115 }
73116
117+ driver .healthWg .Add (2 )
118+ go driver .healthMonitoringLoop (ctx )
119+ go driver .watchHealthOverrides (ctx )
120+
74121 return driver , nil
75122}
76123
77124func (d * driver ) Shutdown (logger klog.Logger ) error {
78125 if d .healthcheck != nil {
79126 d .healthcheck .Stop (logger )
80127 }
128+
129+ logger .Info ("Stopping device health monitoring" )
130+ close (d .stopHealthCh )
131+
132+ d .clientsMu .Lock ()
133+ for _ , clientCh := range d .healthClients {
134+ close (clientCh )
135+ }
136+ d .healthClients = nil
137+ d .clientsMu .Unlock ()
138+
139+ d .healthWg .Wait ()
140+
81141 d .helper .Stop ()
82142 return nil
83143}
@@ -144,3 +204,235 @@ func (d *driver) HandleError(ctx context.Context, err error, msg string) {
144204 d .cancelCtx (fmt .Errorf ("fatal background error: %w" , err ))
145205 }
146206}
207+
208+ func (d * driver ) healthMonitoringLoop (ctx context.Context ) {
209+ defer d .healthWg .Done ()
210+
211+ logger := klog .FromContext (ctx )
212+ ticker := time .NewTicker (30 * time .Second )
213+ defer ticker .Stop ()
214+
215+ logger .Info ("Starting device health monitoring loop" )
216+ d .performHealthCheck (logger )
217+
218+ for {
219+ select {
220+ case <- d .stopHealthCh :
221+ logger .Info ("Health monitoring loop stopped" )
222+ return
223+ case <- ctx .Done ():
224+ return
225+ case <- ticker .C :
226+ d .performHealthCheck (logger )
227+ }
228+ }
229+ }
230+
231+ const healthAnnotationPrefix = "health.example.com/"
232+
233+ func (d * driver ) watchHealthOverrides (ctx context.Context ) {
234+ defer d .healthWg .Done ()
235+
236+ logger := klog .FromContext (ctx )
237+ podName , _ := os .Hostname ()
238+ namespace := os .Getenv ("NAMESPACE" )
239+ if podName == "" || namespace == "" {
240+ logger .Info ("Pod identity not available, health overrides disabled" )
241+ return
242+ }
243+
244+ for {
245+ watcher , err := d .client .CoreV1 ().Pods (namespace ).Watch (ctx , metav1.ListOptions {
246+ FieldSelector : "metadata.name=" + podName ,
247+ })
248+ if err != nil {
249+ logger .Error (err , "Failed to watch pod for health overrides" )
250+ select {
251+ case <- ctx .Done ():
252+ return
253+ case <- d .stopHealthCh :
254+ return
255+ case <- time .After (5 * time .Second ):
256+ continue
257+ }
258+ }
259+
260+ for event := range watcher .ResultChan () {
261+ select {
262+ case <- ctx .Done ():
263+ watcher .Stop ()
264+ return
265+ case <- d .stopHealthCh :
266+ watcher .Stop ()
267+ return
268+ default :
269+ }
270+
271+ pod , ok := event .Object .(* corev1.Pod )
272+ if ! ok {
273+ continue
274+ }
275+ d .applyHealthOverrides (logger , pod .Annotations )
276+ }
277+ }
278+ }
279+
280+ func (d * driver ) applyHealthOverrides (logger klog.Logger , annotations map [string ]string ) {
281+ d .healthMu .Lock ()
282+
283+ activeOverrides := make (map [string ]bool )
284+ for key , value := range annotations {
285+ if ! strings .HasPrefix (key , healthAnnotationPrefix ) {
286+ continue
287+ }
288+ deviceName := strings .TrimPrefix (key , healthAnnotationPrefix )
289+ if _ , exists := d .deviceHealth [deviceName ]; ! exists {
290+ continue
291+ }
292+
293+ activeOverrides [deviceName ] = true
294+ if d .healthOverrides [deviceName ] {
295+ continue
296+ }
297+
298+ var scenario HealthScenario
299+ switch strings .ToLower (value ) {
300+ case "unhealthy" :
301+ scenario = ScenarioTemperatureWarning
302+ case "unknown" :
303+ scenario = ScenarioCommunicationFailure
304+ default :
305+ scenario = ScenarioHealthy
306+ }
307+
308+ d .simulator .ForceScenario (deviceName , scenario )
309+ d .healthOverrides [deviceName ] = true
310+ logger .Info ("Health override applied" , "device" , deviceName , "status" , value )
311+ }
312+
313+ for deviceName := range d .healthOverrides {
314+ if ! activeOverrides [deviceName ] {
315+ d .simulator .ForceScenario (deviceName , ScenarioHealthy )
316+ delete (d .healthOverrides , deviceName )
317+ logger .Info ("Health override removed, returning to simulation" , "device" , deviceName )
318+ }
319+ }
320+
321+ d .healthMu .Unlock ()
322+
323+ d .performHealthCheck (logger )
324+ }
325+
326+ func (d * driver ) performHealthCheck (logger klog.Logger ) {
327+ d .healthMu .Lock ()
328+
329+ hasChanges := false
330+ for deviceName , currentHealth := range d .deviceHealth {
331+ newHealth , newMessage := d .simulator .GetDeviceHealth (deviceName )
332+ if currentHealth .Health != newHealth || currentHealth .Message != newMessage {
333+ currentHealth .Health = newHealth
334+ currentHealth .Message = newMessage
335+ hasChanges = true
336+ logger .Info ("Device health changed" ,
337+ "device" , deviceName ,
338+ "health" , newHealth .String (),
339+ "message" , newMessage )
340+ }
341+ }
342+
343+ d .healthMu .Unlock ()
344+
345+ if hasChanges {
346+ d .notifyClients ()
347+ }
348+ }
349+
350+ func (d * driver ) buildHealthResponse () * drahealthv1alpha1.NodeWatchResourcesResponse {
351+ var devices []* drahealthv1alpha1.DeviceHealth
352+ for deviceName , health := range d .deviceHealth {
353+ devices = append (devices , & drahealthv1alpha1.DeviceHealth {
354+ Device : & drahealthv1alpha1.DeviceIdentifier {
355+ PoolName : d .poolName ,
356+ DeviceName : deviceName ,
357+ },
358+ Health : health .Health ,
359+ LastUpdatedTime : time .Now ().Unix (),
360+ Message : health .Message ,
361+ HealthCheckTimeoutSeconds : 60 ,
362+ })
363+ }
364+
365+ return & drahealthv1alpha1.NodeWatchResourcesResponse {
366+ Devices : devices ,
367+ }
368+ }
369+
370+ func (d * driver ) notifyClients () {
371+ d .healthMu .RLock ()
372+ response := d .buildHealthResponse ()
373+ d .healthMu .RUnlock ()
374+
375+ d .clientsMu .RLock ()
376+ defer d .clientsMu .RUnlock ()
377+
378+ for _ , clientCh := range d .healthClients {
379+ select {
380+ case clientCh <- response :
381+ default :
382+ }
383+ }
384+ }
385+
386+ func (d * driver ) NodeWatchResources (
387+ req * drahealthv1alpha1.NodeWatchResourcesRequest ,
388+ stream grpc.ServerStreamingServer [drahealthv1alpha1.NodeWatchResourcesResponse ],
389+ ) error {
390+ logger := klog .FromContext (stream .Context ())
391+ logger .Info ("New health monitoring client connected" )
392+
393+ // Create a channel for this client
394+ clientCh := make (chan * drahealthv1alpha1.NodeWatchResourcesResponse , 10 )
395+
396+ // Register the client
397+ d .clientsMu .Lock ()
398+ d .healthClients = append (d .healthClients , clientCh )
399+ d .clientsMu .Unlock ()
400+
401+ // Cleanup on exit
402+ defer func () {
403+ d .clientsMu .Lock ()
404+ for i , ch := range d .healthClients {
405+ if ch == clientCh {
406+ d .healthClients = append (d .healthClients [:i ], d .healthClients [i + 1 :]... )
407+ break
408+ }
409+ }
410+ d .clientsMu .Unlock ()
411+ close (clientCh )
412+ logger .Info ("Health monitoring client disconnected" )
413+ }()
414+
415+ d .healthMu .RLock ()
416+ initialResponse := d .buildHealthResponse ()
417+ d .healthMu .RUnlock ()
418+
419+ if err := stream .Send (initialResponse ); err != nil {
420+ return fmt .Errorf ("failed to send initial health status: %w" , err )
421+ }
422+
423+ // Stream updates
424+ for {
425+ select {
426+ case <- stream .Context ().Done ():
427+ return stream .Context ().Err ()
428+ case response , ok := <- clientCh :
429+ if ! ok {
430+ // Channel closed, exit
431+ return nil
432+ }
433+ if err := stream .Send (response ); err != nil {
434+ return fmt .Errorf ("failed to send health update: %w" , err )
435+ }
436+ }
437+ }
438+ }
0 commit comments