mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2025-03-05 08:17:04 +00:00
nfd-worker: use fsnotify for watching for config file changes
Add support for detecting configuration file changes via file system notifications (fsnotify). Watches are added for the whole directory chain (up to root directory) so that all changes (even directory renames) affecting the given configuration file path are captured. Previously dynamic (re-)configuration of nfd-worker was implemented by (re-)reading the configuration file on every labeling pass. This was simple and effective, even if a bit wasteful. However, it didn't provide asynchronous configuration updates that will be required for e.g. controlling the "sleep-interval" parameter dynamically which will be implemented by later patches.
This commit is contained in:
parent
6958a6677f
commit
b6ff514853
2 changed files with 77 additions and 7 deletions
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.15
|
|||
|
||||
require (
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/klauspost/cpuid/v2 v2.0.2
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
|
|
|
@ -24,10 +24,12 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
@ -178,6 +180,40 @@ func NewNfdWorker(args Args) (NfdWorker, error) {
|
|||
return nfd, nil
|
||||
}
|
||||
|
||||
func addConfigWatch(path string) (*fsnotify.Watcher, map[string]struct{}, error) {
|
||||
paths := make(map[string]struct{})
|
||||
|
||||
// Create watcher
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return w, paths, fmt.Errorf("failed to create fsnotify watcher: %v", err)
|
||||
}
|
||||
|
||||
// Add watches for all directory components so that we catch e.g. renames
|
||||
// upper in the tree
|
||||
added := false
|
||||
for p := path; ; p = filepath.Dir(p) {
|
||||
|
||||
if err := w.Add(p); err != nil {
|
||||
stdoutLogger.Printf("failed to add fsnotify watch for %q: %v", p, err)
|
||||
} else {
|
||||
stdoutLogger.Printf("added fsnotify watch %q", p)
|
||||
added = true
|
||||
}
|
||||
|
||||
paths[p] = struct{}{}
|
||||
if filepath.Dir(p) == p {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !added {
|
||||
// Want to be sure that we watch something
|
||||
return w, paths, fmt.Errorf("failed to add any watch")
|
||||
}
|
||||
return w, paths, nil
|
||||
}
|
||||
|
||||
// Run NfdWorker client. Returns if a fatal error is encountered, or, after
|
||||
// one request if OneShot is set to 'true' in the worker args.
|
||||
func (w *nfdWorker) Run() error {
|
||||
|
@ -191,13 +227,19 @@ func (w *nfdWorker) Run() error {
|
|||
}
|
||||
defer w.disconnect()
|
||||
|
||||
trigger := time.After(0)
|
||||
// Create watcher for config file and read initial configuration
|
||||
configFilePath := filepath.Clean(w.args.ConfigFile)
|
||||
configWatch, paths, err := addConfigWatch(configFilePath)
|
||||
if err != nil {
|
||||
return (err)
|
||||
}
|
||||
w.configure(configFilePath, w.args.Options)
|
||||
|
||||
labelTrigger := time.After(0)
|
||||
var configTrigger <-chan time.Time
|
||||
for {
|
||||
select {
|
||||
case <-trigger:
|
||||
// Parse and apply configuration
|
||||
w.configure(w.args.ConfigFile, w.args.Options)
|
||||
|
||||
case <-labelTrigger:
|
||||
// Get the set of feature labels.
|
||||
labels := createFeatureLabels(w.sources, w.labelWhiteList)
|
||||
|
||||
|
@ -214,11 +256,38 @@ func (w *nfdWorker) Run() error {
|
|||
}
|
||||
|
||||
if w.args.SleepInterval > 0 {
|
||||
trigger = time.After(w.args.SleepInterval)
|
||||
labelTrigger = time.After(w.args.SleepInterval)
|
||||
}
|
||||
|
||||
case e := <-configWatch.Events:
|
||||
name := filepath.Clean(e.Name)
|
||||
|
||||
// If any of our paths (directories or the file itself) change
|
||||
if _, ok := paths[name]; ok {
|
||||
stdoutLogger.Printf("fsnotify event in %q detected, reconfiguring fsnotify and reloading configuration", name)
|
||||
|
||||
// Blindly remove existing watch and add a new one
|
||||
if err := configWatch.Close(); err != nil {
|
||||
stderrLogger.Printf("WARNING: failed to close fsnotify watcher: %v", err)
|
||||
}
|
||||
configWatch, paths, err = addConfigWatch(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rate limiter. In certain filesystem operations we get
|
||||
// numerous events in quick succession and we only want one
|
||||
// config re-load
|
||||
configTrigger = time.After(time.Second)
|
||||
}
|
||||
|
||||
case e := <-configWatch.Errors:
|
||||
stderrLogger.Printf("ERROR: config file watcher error: %v", e)
|
||||
|
||||
case <-configTrigger:
|
||||
w.configure(configFilePath, w.args.Options)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// connect creates a client connection to the NFD master
|
||||
|
|
Loading…
Add table
Reference in a new issue