@@ -3,7 +3,9 @@ package daemon
33import (
44 "bufio"
55 "cmp"
6+ "crypto/sha256"
67 "fmt"
8+ "io"
79 "net"
810 "os"
911 "os/exec"
@@ -73,6 +75,13 @@ var ptpProcesses = []string{
7375 chronydProcessName , // there can be only one chronyd process in the system
7476}
7577
78+ // saFileInfo tracks authentication file information for a profile
79+ type saFileInfo struct {
80+ profileName string
81+ filePath string
82+ fileHash string
83+ }
84+
7685var ptpTmpFiles = []string {
7786 ts2phcProcessName ,
7887 syncEProcessName ,
@@ -306,6 +315,10 @@ type Daemon struct {
306315
307316 // Allow vendors to include plugins
308317 pluginManager plugin.PluginManager
318+
319+ // Track sa_file (authentication) files for monitoring changes
320+ saFileTracker map [string ]* saFileInfo
321+ saFileMutex sync.Mutex
309322}
310323
311324// New LinuxPTP is called by daemon to generate new linuxptp instance
@@ -348,19 +361,43 @@ func New(
348361 processManager : pm ,
349362 readyTracker : tracker ,
350363 stopCh : stopCh ,
364+ saFileTracker : make (map [string ]* saFileInfo ),
351365 }
352366}
353367
354368// Run in a for loop to listen for any LinuxPTPConfUpdate changes
369+ // This function handles two types of configuration changes:
370+ // 1. PtpConfig changes (via ConfigMap) - triggers UpdateCh
371+ // 2. Authentication file changes (via Secret) - triggers sa_file check
372+ // Both trigger applyNodePTPProfiles() which restarts PTP processes WITHOUT restarting the pod
355373func (dn * Daemon ) Run () {
356374 go dn .processManager .ptpEventHandler .ProcessEvents ()
375+
376+ // Start sa_file monitoring ticker (check every 5 seconds for faster detection)
377+ // This detects when Kubernetes updates the mounted secret file
378+ // Total delay = Kubernetes propagation (1-10s) + our check interval (0-5s)
379+ saFileCheckTicker := time .NewTicker (5 * time .Second )
380+ defer saFileCheckTicker .Stop ()
381+
357382 for {
358383 select {
359384 case <- dn .ptpUpdate .UpdateCh :
385+ // PtpConfig change detected via ConfigMap
386+ glog .Info ("PtpConfig change detected, restarting PTP processes" )
360387 err := dn .applyNodePTPProfiles ()
361388 if err != nil {
362389 glog .Errorf ("linuxPTP apply node profile failed: %v" , err )
363390 }
391+ case <- saFileCheckTicker .C :
392+ // Check for sa_file (authentication) changes
393+ // When Secret is updated, Kubernetes automatically updates the mounted file
394+ if dn .checkSaFileChanges () {
395+ glog .Info ("sa_file authentication file changed, restarting PTP processes" )
396+ err := dn .applyNodePTPProfiles ()
397+ if err != nil {
398+ glog .Errorf ("linuxPTP apply node profile failed after sa_file change: %v" , err )
399+ }
400+ }
364401 case <- dn .stopCh :
365402 dn .stopAllProcesses ()
366403 glog .Infof ("linuxPTP stop signal received, existing.." )
@@ -369,6 +406,131 @@ func (dn *Daemon) Run() {
369406 }
370407}
371408
409+ // computeFileHash computes SHA256 hash of a file for change detection
410+ // This is completely generic and works with any file content (binary, text, any format)
411+ // Used to detect when Kubernetes updates mounted secret files
412+ func computeFileHash (filePath string ) (string , error ) {
413+ file , err := os .Open (filePath )
414+ if err != nil {
415+ return "" , err
416+ }
417+ defer file .Close ()
418+
419+ hash := sha256 .New ()
420+ if _ , err := io .Copy (hash , file ); err != nil {
421+ return "" , err
422+ }
423+
424+ return fmt .Sprintf ("%x" , hash .Sum (nil )), nil
425+ }
426+
427+ // extractSaFileFromConf extracts sa_file path from ptp4lConf if present
428+ func extractSaFileFromConf (ptp4lConf * string ) string {
429+ if ptp4lConf == nil {
430+ return ""
431+ }
432+
433+ // Parse the config to extract sa_file from [global] section
434+ for _ , line := range strings .Split (* ptp4lConf , "\n " ) {
435+ line = strings .TrimSpace (line )
436+ // Skip comments
437+ if strings .HasPrefix (line , "#" ) {
438+ continue
439+ }
440+ // Look for sa_file option
441+ if strings .HasPrefix (line , "sa_file" ) {
442+ parts := strings .Fields (line )
443+ if len (parts ) >= 2 {
444+ return parts [1 ]
445+ }
446+ }
447+ }
448+ return ""
449+ }
450+
451+ // checkSaFileChanges checks if any tracked sa_file has changed
452+ // When a Secret is updated in Kubernetes, the mounted file is automatically updated
453+ // This function detects those changes via hash comparison and returns true if any file changed
454+ // This triggers a PTP process restart (not pod restart) - same as ConfigMap changes
455+ func (dn * Daemon ) checkSaFileChanges () bool {
456+ dn .saFileMutex .Lock ()
457+ defer dn .saFileMutex .Unlock ()
458+
459+ changed := false
460+ for _ , info := range dn .saFileTracker {
461+ if info .filePath == "" {
462+ continue
463+ }
464+
465+ // Check if file exists
466+ if _ , err := os .Stat (info .filePath ); os .IsNotExist (err ) {
467+ glog .Warningf ("sa_file %s for profile %s does not exist" , info .filePath , info .profileName )
468+ continue
469+ }
470+
471+ // Compute current hash
472+ currentHash , err := computeFileHash (info .filePath )
473+ if err != nil {
474+ glog .Errorf ("Failed to compute hash for sa_file %s: %v" , info .filePath , err )
475+ continue
476+ }
477+
478+ // Compare with stored hash
479+ if currentHash != info .fileHash {
480+ glog .Infof ("sa_file %s for profile %s has changed (old hash: %.16s... -> %.16s...)" ,
481+ info .filePath , info .profileName , info .fileHash , currentHash )
482+ info .fileHash = currentHash
483+ changed = true
484+ }
485+ }
486+
487+ return changed
488+ }
489+
490+ // updateSaFileTracking updates the tracking information for sa_files from current profiles
491+ // Called after profiles are applied to initialize or update sa_file monitoring
492+ // Extracts sa_file paths from ptp4lConf and sets up hash-based change detection
493+ func (dn * Daemon ) updateSaFileTracking () {
494+ dn .saFileMutex .Lock ()
495+ defer dn .saFileMutex .Unlock ()
496+
497+ // Clear old tracking
498+ dn .saFileTracker = make (map [string ]* saFileInfo )
499+
500+ // Add tracking for each profile that has sa_file configured
501+ for _ , profile := range dn .ptpUpdate .NodeProfiles {
502+ if profile .Name == nil {
503+ continue
504+ }
505+
506+ saFilePath := extractSaFileFromConf (profile .Ptp4lConf )
507+ if saFilePath == "" {
508+ continue
509+ }
510+
511+ glog .Infof ("Tracking sa_file %s for profile %s" , saFilePath , * profile .Name )
512+
513+ // Compute initial hash
514+ hash := ""
515+ if _ , err := os .Stat (saFilePath ); err == nil {
516+ if h , err := computeFileHash (saFilePath ); err == nil {
517+ hash = h
518+ glog .Infof ("Initialized tracking for sa_file %s (hash: %.16s...)" , saFilePath , h )
519+ } else {
520+ glog .Warningf ("Failed to compute initial hash for sa_file %s: %v" , saFilePath , err )
521+ }
522+ } else {
523+ glog .Warningf ("sa_file %s does not exist yet for profile %s" , saFilePath , * profile .Name )
524+ }
525+
526+ dn .saFileTracker [* profile .Name ] = & saFileInfo {
527+ profileName : * profile .Name ,
528+ filePath : saFilePath ,
529+ fileHash : hash ,
530+ }
531+ }
532+ }
533+
372534func printWhenNotNil (p interface {}, description string ) {
373535 switch v := p .(type ) {
374536 case * string :
@@ -502,6 +664,10 @@ func (dn *Daemon) applyNodePTPProfiles() error {
502664 dn .pluginManager .PopulateHwConfig (dn .hwconfigs )
503665 * dn .refreshNodePtpDevice = true
504666 dn .readyTracker .setConfig (true )
667+
668+ // Update sa_file tracking after profiles are applied
669+ dn .updateSaFileTracking ()
670+
505671 return nil
506672}
507673
0 commit comments