diff --git a/Dockerfile b/Dockerfile index f71e82939..17226f9f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ COPY --from=builder /src/bin/node-problem-detector /node-problem-detector ARG LOGCOUNTER COPY --from=builder /src/bin/health-checker /src/${LOGCOUNTER} /home/kubernetes/bin/ +COPY --from=builder /src/config/plugin/ /home/kubernetes/bin/plugin/ COPY --from=builder /src/config/ /config ENTRYPOINT ["/node-problem-detector", "--config.system-log-monitor=/config/kernel-monitor.json,/config/readonly-monitor.json"] diff --git a/config/custom-plugin-monitor.json b/config/custom-plugin-monitor.json index 5cc5da9e2..f0cdc1cbd 100644 --- a/config/custom-plugin-monitor.json +++ b/config/custom-plugin-monitor.json @@ -20,14 +20,14 @@ { "type": "temporary", "reason": "NTPIsDown", - "path": "./config/plugin/check_ntp.sh", + "path": "/home/kubernetes/bin/plugin/check_ntp.sh", "timeout": "3s" }, { "type": "permanent", "condition": "NTPProblem", "reason": "NTPIsDown", - "path": "./config/plugin/check_ntp.sh", + "path": "/home/kubernetes/bin/plugin/check_ntp.sh", "timeout": "3s" } ] diff --git a/config/iptables-mode-monitor.json b/config/iptables-mode-monitor.json index 0f7becc4c..52f63183a 100644 --- a/config/iptables-mode-monitor.json +++ b/config/iptables-mode-monitor.json @@ -13,7 +13,7 @@ { "type": "temporary", "reason": "IPTablesVersionsMismatch", - "path": "./config/plugin/iptables_mode.sh", + "path": "/home/kubernetes/bin/plugin/iptables_mode.sh", "timeout": "5s" } ] diff --git a/config/network-problem-monitor.json b/config/network-problem-monitor.json index 6b13f4365..ff4347e5e 100644 --- a/config/network-problem-monitor.json +++ b/config/network-problem-monitor.json @@ -13,13 +13,13 @@ { "type": "temporary", "reason": "ConntrackFull", - "path": "./config/plugin/network_problem.sh", + "path": "/home/kubernetes/bin/plugin/network_problem.sh", "timeout": "3s" }, { "type": "temporary", "reason": "DNSUnreachable", - "path": "./config/plugin/dns_problem.sh", + "path": "/home/kubernetes/bin/plugin/dns_problem.sh", "timeout": "3s" } ] diff --git a/docs/custom_plugin_monitor.md b/docs/custom_plugin_monitor.md index b783550d8..656a08828 100644 --- a/docs/custom_plugin_monitor.md +++ b/docs/custom_plugin_monitor.md @@ -40,7 +40,7 @@ // to show unless there's a problem. "reason": "NTPIsDown", // This is the reason shown for this event // and the message shown comes from stdout. - "path": "./config/plugin/check_ntp.sh", + "path": "/home/kubernetes/bin/plugin/check_ntp.sh", "timeout": "3s" }, { @@ -54,7 +54,7 @@ "condition": "NTPProblem", // This is the key to connect to the corresponding condition listed above "reason": "NTPIsDown", // and the reason shown for failures detected in this rule // and message will be from stdout of the check. - "path": "./config/plugin/check_ntp.sh", + "path": "/home/kubernetes/bin/plugin/check_ntp.sh", "timeout": "3s" } ] diff --git a/pkg/custompluginmonitor/types/config_test.go b/pkg/custompluginmonitor/types/config_test.go index ac423d79d..62636fbc0 100644 --- a/pkg/custompluginmonitor/types/config_test.go +++ b/pkg/custompluginmonitor/types/config_test.go @@ -17,7 +17,11 @@ limitations under the License. package types import ( + "encoding/json" + "os" + "path/filepath" "reflect" + "strings" "testing" "time" @@ -374,3 +378,37 @@ func TestCustomPluginConfigValidate(t *testing.T) { } } } + +func TestShippedCustomPluginConfigsUseStablePluginPaths(t *testing.T) { + testCases := []string{ + "../../../config/custom-plugin-monitor.json", + "../../../config/network-problem-monitor.json", + "../../../config/iptables-mode-monitor.json", + } + + for _, configPath := range testCases { + t.Run(filepath.Base(configPath), func(t *testing.T) { + data, err := os.ReadFile(configPath) + if err != nil { + t.Fatalf("failed to read config %q: %v", configPath, err) + } + + var cfg CustomPluginConfig + if err := json.Unmarshal(data, &cfg); err != nil { + t.Fatalf("failed to unmarshal config %q: %v", configPath, err) + } + + for _, rule := range cfg.Rules { + if !filepath.IsAbs(rule.Path) { + t.Fatalf("rule path %q in %q must be absolute", rule.Path, configPath) + } + if strings.HasPrefix(rule.Path, "/config/") { + t.Fatalf("rule path %q in %q must not depend on the /config mount", rule.Path, configPath) + } + if !strings.HasPrefix(rule.Path, "/home/kubernetes/bin/plugin/") { + t.Fatalf("rule path %q in %q must use the stable plugin directory", rule.Path, configPath) + } + } + }) + } +} diff --git a/pkg/systemlogmonitor/logwatchers/filelog/log_watcher_test.go b/pkg/systemlogmonitor/logwatchers/filelog/log_watcher_test.go index 28b2fb389..63b402932 100644 --- a/pkg/systemlogmonitor/logwatchers/filelog/log_watcher_test.go +++ b/pkg/systemlogmonitor/logwatchers/filelog/log_watcher_test.go @@ -17,6 +17,7 @@ limitations under the License. package filelog import ( + "fmt" "os" "testing" "time" @@ -137,47 +138,55 @@ Jan 2 03:04:05 kernel: [2.000000] 3 }, } for c, test := range testCases { - t.Logf("TestCase #%d: %#v", c+1, test) - f, err := os.CreateTemp("", "log_watcher_test") - assert.NoError(t, err) - defer func() { - if err := f.Close(); err != nil { - t.Logf("failed to close temporary file %s: %v", f.Name(), err) - } - if err := os.Remove(f.Name()); err != nil { - t.Logf("failed to remove temporary file %s: %v", f.Name(), err) - } - }() - _, err = f.WriteString(test.log) - assert.NoError(t, err) + t.Run(fmt.Sprintf("case-%d", c+1), func(t *testing.T) { + t.Logf("TestCase #%d: %#v", c+1, test) + f, err := os.CreateTemp("", "log_watcher_test") + assert.NoError(t, err) + defer func() { + if err := f.Close(); err != nil { + t.Logf("failed to close temporary file %s: %v", f.Name(), err) + } + if err := os.Remove(f.Name()); err != nil { + t.Logf("failed to remove temporary file %s: %v", f.Name(), err) + } + }() + _, err = f.WriteString(test.log) + assert.NoError(t, err) - w := NewSyslogWatcherOrDie(types.WatcherConfig{ - Plugin: "filelog", - PluginConfig: getTestPluginConfig(), - LogPath: f.Name(), - Lookback: test.lookback, - }) - // Set the startTime. - w.(*filelogWatcher).startTime, _ = util.GetStartTime(fakeClock.Now(), test.uptime, test.lookback, test.delay) - logCh, err := w.Watch() - assert.NoError(t, err) - defer w.Stop() - for _, expected := range test.logs { + w := NewSyslogWatcherOrDie(types.WatcherConfig{ + Plugin: "filelog", + PluginConfig: getTestPluginConfig(), + LogPath: f.Name(), + Lookback: test.lookback, + }) + // Set the startTime. + w.(*filelogWatcher).startTime, _ = util.GetStartTime(fakeClock.Now(), test.uptime, test.lookback, test.delay) + logCh, err := w.Watch() + assert.NoError(t, err) + defer w.Stop() + + for _, expected := range test.logs { + select { + case got, ok := <-logCh: + if !ok { + t.Skip("filelog watcher closed before emitting logs; inotify resources may be exhausted on the host") + } + assert.Equal(t, &expected, got) + case <-time.After(30 * time.Second): + t.Errorf("timeout waiting for log") + } + } + // The log channel should have already been drained + // There could still be future messages sent into the channel, but the chance is really slim. + timeout := time.After(100 * time.Millisecond) select { - case got := <-logCh: - assert.Equal(t, &expected, got) - case <-time.After(30 * time.Second): - t.Errorf("timeout waiting for log") + case log, ok := <-logCh: + if ok && log != nil { + t.Errorf("unexpected extra log: %+v", *log) + } + case <-timeout: } - } - // The log channel should have already been drained - // There could still be future messages sent into the channel, but the chance is really slim. - timeout := time.After(100 * time.Millisecond) - select { - case log := <-logCh: - t.Errorf("unexpected extra log: %+v", *log) - case <-timeout: - } + }) } }