diff --git a/.travis.yml b/.travis.yml index 986a433f9..85bb80bdb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,3 +31,4 @@ script: - BUILD_TAGS="disable_stackdriver_exporter" make test - make clean && ENABLE_JOURNALD=0 make - ENABLE_JOURNALD=0 make test + - ENABLE_JOURNALD=0 make build-binaries diff --git a/Makefile b/Makefile index 3fecc6c77..f1a49e3fa 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,12 @@ UPLOAD_PATH:=$(shell echo $(UPLOAD_PATH) | sed '$$s/\/*$$//') PKG:=k8s.io/node-problem-detector # PKG_SOURCES are all the go source code. +ifeq ($(OS),Windows_NT) +PKG_SOURCES:= +# TODO: File change detection does not work in Windows. +else PKG_SOURCES:=$(shell find pkg cmd -name '*.go') +endif # PARALLEL specifies the number of parallel test nodes to run for e2e tests. PARALLEL?=3 @@ -74,6 +79,12 @@ BUILD_TAGS?= LINUX_BUILD_TAGS = $(BUILD_TAGS) WINDOWS_BUILD_TAGS = $(BUILD_TAGS) +ifeq ($(OS),Windows_NT) +HOST_PLATFORM_BUILD_TAGS = $(WINDOWS_BUILD_TAGS) +else +HOST_PLATFORM_BUILD_TAGS = $(LINUX_BUILD_TAGS) +endif + ifeq ($(ENABLE_JOURNALD), 1) # Enable journald build tag. LINUX_BUILD_TAGS := $(BUILD_TAGS) journald @@ -91,9 +102,9 @@ else endif vet: - GO111MODULE=on go list -mod vendor -tags "$(LINUX_BUILD_TAGS)" ./... | \ + GO111MODULE=on go list -mod vendor -tags "$(HOST_PLATFORM_BUILD_TAGS)" ./... | \ grep -v "./vendor/*" | \ - GO111MODULE=on xargs go vet -mod vendor -tags "$(LINUX_BUILD_TAGS)" + GO111MODULE=on xargs go vet -mod vendor -tags "$(HOST_PLATFORM_BUILD_TAGS)" fmt: find . -type f -name "*.go" | grep -v "./vendor/*" | xargs gofmt -s -w -l @@ -109,7 +120,10 @@ ifeq ($(ENABLE_JOURNALD), 1) LINUX_AMD64_BINARIES += bin/linux_amd64/log-counter endif -windows-binaries: $(WINDOWS_AMD64_BINARIES) $(WINDOWS_AMD64_TEST_BINARIES) +WINDOWS_BINARIES = $(WINDOWS_AMD64_BINARIES) $(WINDOWS_AMD64_TEST_BINARIES) +LINUX_BINARIES = $(LINUX_AMD64_BINARIES) $(LINUX_AMD64_TEST_BINARIES) + +windows-binaries: $(WINDOWS_BINARIES) bin/windows_amd64/%.exe: $(PKG_SOURCES) ifeq ($(ENABLE_JOURNALD), 1) @@ -191,10 +205,10 @@ endif cmd/healthchecker/health_checker.go test: vet fmt - GO111MODULE=on go test -mod vendor -timeout=1m -v -race -short -tags "$(LINUX_BUILD_TAGS)" ./... + GO111MODULE=on go test -mod vendor -timeout=1m -v -race -short -tags "$(HOST_PLATFORM_BUILD_TAGS)" ./... e2e-test: vet fmt build-tar - GO111MODULE=on ginkgo -nodes=$(PARALLEL) -mod vendor -timeout=10m -v -tags "$(LINUX_BUILD_TAGS)" -stream \ + GO111MODULE=on ginkgo -nodes=$(PARALLEL) -mod vendor -timeout=10m -v -tags "$(HOST_PLATFORM_BUILD_TAGS)" -stream \ ./test/e2e/metriconly/... -- \ -project=$(PROJECT) -zone=$(ZONE) \ -image=$(VM_IMAGE) -image-family=$(IMAGE_FAMILY) -image-project=$(IMAGE_PROJECT) \ @@ -203,7 +217,7 @@ e2e-test: vet fmt build-tar -boskos-project-type=$(BOSKOS_PROJECT_TYPE) -job-name=$(JOB_NAME) \ -artifacts-dir=$(ARTIFACTS) -build-binaries: ./bin/node-problem-detector ./bin/log-counter ./bin/health-checker +build-binaries: ./bin/node-problem-detector ./bin/log-counter ./bin/health-checker $(WINDOWS_BINARIES) $(LINUX_BINARIES) build-container: build-binaries Dockerfile docker build -t $(IMAGE) --build-arg BASEIMAGE=$(BASEIMAGE) --build-arg LOGCOUNTER=$(LOGCOUNTER) . diff --git a/config/windows-containerd-monitor-filelog.json b/config/windows-containerd-monitor-filelog.json index 8404c2a55..1c9a36aae 100644 --- a/config/windows-containerd-monitor-filelog.json +++ b/config/windows-containerd-monitor-filelog.json @@ -8,13 +8,18 @@ "logPath": "C:\\Program Files\\containerd\\containerd.log", "lookback": "5m", "bufferSize": 10, - "source": "docker-monitor", + "source": "containerd", "conditions": [], "rules": [ { "type": "temporary", - "reason": "BadCNIConfig", - "pattern": "failed to reload cni configuration.*" + "reason": "MissingPigz", + "pattern": "unpigz not found.*" + }, + { + "type": "temporary", + "reason": "IncompatibleContainer", + "pattern": ".*CreateComputeSystem.*" } ] } diff --git a/pkg/custompluginmonitor/plugin/plugin.go b/pkg/custompluginmonitor/plugin/plugin.go index 4a548dbaf..c4303ab59 100644 --- a/pkg/custompluginmonitor/plugin/plugin.go +++ b/pkg/custompluginmonitor/plugin/plugin.go @@ -29,6 +29,7 @@ import ( "github.com/golang/glog" cpmtypes "k8s.io/node-problem-detector/pkg/custompluginmonitor/types" + "k8s.io/node-problem-detector/pkg/util" "k8s.io/node-problem-detector/pkg/util/tomb" ) @@ -147,12 +148,7 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp } defer cancel() - // create a process group - sysProcAttr := &syscall.SysProcAttr{ - Setpgid: true, - } - cmd := exec.Command(rule.Path, rule.Args...) - cmd.SysProcAttr = sysProcAttr + cmd := util.Exec(rule.Path, rule.Args...) stdoutPipe, err := cmd.StdoutPipe() if err != nil { @@ -172,6 +168,9 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp waitChan := make(chan struct{}) defer close(waitChan) + var m sync.Mutex + timeout := false + go func() { select { case <-ctx.Done(): @@ -183,7 +182,12 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp glog.Errorf("Error in cmd.Process check %q", rule.Path) break } - err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + + m.Lock() + timeout = true + m.Unlock() + + err := util.Kill(cmd) if err != nil { glog.Errorf("Error in kill process %d, %v", cmd.Process.Pid, err) } @@ -202,12 +206,12 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp wg.Add(2) go func() { + defer wg.Done() stdout, stdoutErr = readFromReader(stdoutPipe, maxCustomPluginBufferBytes) - wg.Done() }() go func() { + defer wg.Done() stderr, stderrErr = readFromReader(stderrPipe, maxCustomPluginBufferBytes) - wg.Done() }() // This will wait for the reads to complete. If the execution times out, the pipes // will be closed and the wait group unblocks. @@ -234,7 +238,11 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp output = string(stdout) output = strings.TrimSpace(output) - if cmd.ProcessState.Sys().(syscall.WaitStatus).Signaled() { + m.Lock() + cmdKilled := timeout + m.Unlock() + + if cmdKilled { output = fmt.Sprintf("Timeout when running plugin %q: state - %s. output - %q", rule.Path, cmd.ProcessState.String(), output) } @@ -257,6 +265,7 @@ func (p *Plugin) run(rule cpmtypes.CustomRule) (exitStatus cpmtypes.Status, outp } } +// Stop the plugin. func (p *Plugin) Stop() { p.tomb.Stop() glog.Info("Stop plugin execution") diff --git a/pkg/custompluginmonitor/plugin/plugin_test.go b/pkg/custompluginmonitor/plugin/plugin_test.go index 845c356c1..e6b558304 100644 --- a/pkg/custompluginmonitor/plugin/plugin_test.go +++ b/pkg/custompluginmonitor/plugin/plugin_test.go @@ -17,6 +17,7 @@ limitations under the License. package plugin import ( + "runtime" "testing" "time" @@ -25,6 +26,13 @@ import ( func TestNewPluginRun(t *testing.T) { ruleTimeout := 1 * time.Second + timeoutExitStatus := cpmtypes.Unknown + ext := "sh" + + if runtime.GOOS == "windows" { + ext = "cmd" + timeoutExitStatus = cpmtypes.NonOK + } utMetas := map[string]struct { Rule cpmtypes.CustomRule @@ -33,7 +41,7 @@ func TestNewPluginRun(t *testing.T) { }{ "ok": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/ok.sh", + Path: "./test-data/ok." + ext, Timeout: &ruleTimeout, }, ExitStatus: cpmtypes.OK, @@ -41,7 +49,7 @@ func TestNewPluginRun(t *testing.T) { }, "non-ok": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/non-ok.sh", + Path: "./test-data/non-ok." + ext, Timeout: &ruleTimeout, }, ExitStatus: cpmtypes.NonOK, @@ -49,7 +57,7 @@ func TestNewPluginRun(t *testing.T) { }, "unknown": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/unknown.sh", + Path: "./test-data/unknown." + ext, Timeout: &ruleTimeout, }, ExitStatus: cpmtypes.Unknown, @@ -57,6 +65,7 @@ func TestNewPluginRun(t *testing.T) { }, "non executable": { Rule: cpmtypes.CustomRule{ + // Intentionally run .sh for Windows, this is meant to be not executable. Path: "./test-data/non-executable.sh", Timeout: &ruleTimeout, }, @@ -65,7 +74,7 @@ func TestNewPluginRun(t *testing.T) { }, "longer than 80 stdout with ok exit status": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/longer-than-80-stdout-with-ok-exit-status.sh", + Path: "./test-data/longer-than-80-stdout-with-ok-exit-status." + ext, Timeout: &ruleTimeout, }, ExitStatus: cpmtypes.OK, @@ -73,7 +82,7 @@ func TestNewPluginRun(t *testing.T) { }, "non defined exit status": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/non-defined-exit-status.sh", + Path: "./test-data/non-defined-exit-status." + ext, Timeout: &ruleTimeout, }, ExitStatus: cpmtypes.Unknown, @@ -81,29 +90,32 @@ func TestNewPluginRun(t *testing.T) { }, "sleep 3 second with ok exit status": { Rule: cpmtypes.CustomRule{ - Path: "./test-data/sleep-3-second-with-ok-exit-status.sh", + Path: "./test-data/sleep-3-second-with-ok-exit-status." + ext, Timeout: &ruleTimeout, }, - ExitStatus: cpmtypes.Unknown, - Output: `Timeout when running plugin "./test-data/sleep-3-second-with-ok-exit-status.sh": state - signal: killed. output - ""`, + ExitStatus: timeoutExitStatus, + Output: `Timeout when running plugin "./test-data/sleep-3-second-with-ok-exit-status.` + ext + `": state - signal: killed. output - ""`, }, } - conf := cpmtypes.CustomPluginConfig{} - (&conf).ApplyConfiguration() - p := Plugin{config: conf} - for desp, utMeta := range utMetas { - gotExitStatus, gotOutput := p.run(utMeta.Rule) - // cut at position max_output_length if expected output is longer than max_output_length bytes - if len(utMeta.Output) > *p.config.PluginGlobalConfig.MaxOutputLength { - utMeta.Output = utMeta.Output[:*p.config.PluginGlobalConfig.MaxOutputLength] - } - if gotExitStatus != utMeta.ExitStatus || gotOutput != utMeta.Output { - t.Errorf("%s", desp) - t.Errorf("Error in run plugin and get exit status and output for %q. "+ - "Got exit status: %v, Expected exit status: %v. "+ - "Got output: %q, Expected output: %q", - utMeta.Rule.Path, gotExitStatus, utMeta.ExitStatus, gotOutput, utMeta.Output) - } + for k, v := range utMetas { + desp := k + utMeta := v + t.Run(desp, func(t *testing.T) { + conf := cpmtypes.CustomPluginConfig{} + (&conf).ApplyConfiguration() + p := Plugin{config: conf} + gotExitStatus, gotOutput := p.run(utMeta.Rule) + // cut at position max_output_length if expected output is longer than max_output_length bytes + if len(utMeta.Output) > *p.config.PluginGlobalConfig.MaxOutputLength { + utMeta.Output = utMeta.Output[:*p.config.PluginGlobalConfig.MaxOutputLength] + } + if gotExitStatus != utMeta.ExitStatus || gotOutput != utMeta.Output { + t.Errorf("Error in run plugin and get exit status and output for %q. "+ + "Got exit status: %v, Expected exit status: %v. "+ + "Got output: %q, Expected output: %q", + utMeta.Rule.Path, gotExitStatus, utMeta.ExitStatus, gotOutput, utMeta.Output) + } + }) } } diff --git a/pkg/custompluginmonitor/plugin/test-data/longer-than-80-stdout-with-ok-exit-status.cmd b/pkg/custompluginmonitor/plugin/test-data/longer-than-80-stdout-with-ok-exit-status.cmd new file mode 100644 index 000000000..25d00d49b --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/longer-than-80-stdout-with-ok-exit-status.cmd @@ -0,0 +1,4 @@ +@echo off + +echo 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +exit 0 diff --git a/pkg/custompluginmonitor/plugin/test-data/non-defined-exit-status.cmd b/pkg/custompluginmonitor/plugin/test-data/non-defined-exit-status.cmd new file mode 100644 index 000000000..36aa1cef2 --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/non-defined-exit-status.cmd @@ -0,0 +1,4 @@ +@echo off + +echo NON-DEFINED-EXIT-STATUS +exit 100 diff --git a/pkg/custompluginmonitor/plugin/test-data/non-ok.cmd b/pkg/custompluginmonitor/plugin/test-data/non-ok.cmd new file mode 100644 index 000000000..d8ead74af --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/non-ok.cmd @@ -0,0 +1,4 @@ +@echo off + +echo NonOK +exit 1 diff --git a/pkg/custompluginmonitor/plugin/test-data/ok.cmd b/pkg/custompluginmonitor/plugin/test-data/ok.cmd new file mode 100644 index 000000000..ade62c75a --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/ok.cmd @@ -0,0 +1,4 @@ +@echo off + +echo OK +exit 0 diff --git a/pkg/custompluginmonitor/plugin/test-data/sleep-3-second-with-ok-exit-status.cmd b/pkg/custompluginmonitor/plugin/test-data/sleep-3-second-with-ok-exit-status.cmd new file mode 100644 index 000000000..1e35106eb --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/sleep-3-second-with-ok-exit-status.cmd @@ -0,0 +1,5 @@ +@echo off + +ping 127.0.0.1 -n 3 > nul +echo SLEEP 3S SECOND +exit 0 diff --git a/pkg/custompluginmonitor/plugin/test-data/unknown.cmd b/pkg/custompluginmonitor/plugin/test-data/unknown.cmd new file mode 100644 index 000000000..153bd767f --- /dev/null +++ b/pkg/custompluginmonitor/plugin/test-data/unknown.cmd @@ -0,0 +1,4 @@ +@echo off + +echo UNKNOWN +exit 3 diff --git a/pkg/exporters/k8sexporter/condition/manager_test.go b/pkg/exporters/k8sexporter/condition/manager_test.go index effcb81bd..dc680898f 100644 --- a/pkg/exporters/k8sexporter/condition/manager_test.go +++ b/pkg/exporters/k8sexporter/condition/manager_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/node-problem-detector/pkg/types" problemutil "k8s.io/node-problem-detector/pkg/util" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/clock" ) @@ -53,33 +53,43 @@ func newTestCondition(condition string) types.Condition { func TestNeedUpdates(t *testing.T) { m, _, _ := newTestManager() var c types.Condition - for desc, test := range map[string]struct { + for _, testCase := range []struct { + name string condition string update bool }{ - "Init condition needs update": { + { + name: "Init condition needs update", condition: "TestCondition", update: true, }, - "Same condition doesn't need update": { + { + name: "Same condition doesn't need update", // not set condition, the test will reuse the condition in last case. update: false, }, - "Same condition with different timestamp need update": { + { + name: "Same condition with different timestamp need update", condition: "TestCondition", update: true, }, - "New condition needs update": { + { + name: "New condition needs update", condition: "TestConditionNew", update: true, }, } { - if test.condition != "" { - c = newTestCondition(test.condition) + tc := testCase + t.Log(tc.name) + if tc.condition != "" { + // Guarantee that the time advances before creating a new condition. + for now := time.Now(); now == time.Now(); { + } + c = newTestCondition(tc.condition) } m.UpdateCondition(c) - assert.Equal(t, test.update, m.needUpdates(), desc) - assert.Equal(t, c, m.conditions[c.Type], desc) + assert.Equal(t, tc.update, m.needUpdates(), tc.name) + assert.Equal(t, c, m.conditions[c.Type], tc.name) } } diff --git a/pkg/util/exec_linux.go b/pkg/util/exec_linux.go new file mode 100644 index 000000000..29a9afef5 --- /dev/null +++ b/pkg/util/exec_linux.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "os/exec" + "syscall" +) + +// Exec creates a new process with the specified arguments. +func Exec(name string, arg ...string) *exec.Cmd { + // create a process group + sysProcAttr := &syscall.SysProcAttr{ + Setpgid: true, + } + cmd := exec.Command(name, arg...) + cmd.SysProcAttr = sysProcAttr + return cmd +} + +// Kill the process and subprocesses. +func Kill(cmd *exec.Cmd) error { + if cmd.Process == nil { + return fmt.Errorf("%v does not have a process handle", cmd) + } + return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) +} diff --git a/pkg/util/exec_test.go b/pkg/util/exec_test.go new file mode 100644 index 000000000..9d350cb4a --- /dev/null +++ b/pkg/util/exec_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "runtime" + "testing" +) + +func TestExec(t *testing.T) { + var cmds [][]string + + if runtime.GOOS == "windows" { + cmds = [][]string{ + {"powershell.exe"}, + {"cmd.exe", "/C", "echo", "Hello"}, + {"cmd.exe", "/K", "echo", "Wait", "forever"}, + {"testdata/hello-world.cmd"}, + {"testdata/hello-world.bat"}, + {"testdata/hello-world.ps1"}, + } + } else { + cmds = [][]string{ + {"/bin/sh"}, + {"/bin/bash"}, + } + } + + for _, v := range cmds { + args := v + t.Run(fmt.Sprintf("%v", args), func(t *testing.T) { + cmd := Exec(args[0], args[1:]...) + + if err := Kill(cmd); err == nil { + t.Error("Kill(cmd) expected to have error because of empty handle, got none") + } + + if err := cmd.Start(); err != nil { + t.Errorf("Start() got error, %v", err) + } + + if err := Kill(cmd); err != nil { + t.Errorf("Kill(cmd) for %s %v got error, %v", cmd.Path, cmd.Args, err) + } + }) + } +} diff --git a/pkg/util/exec_windows.go b/pkg/util/exec_windows.go new file mode 100644 index 000000000..2079610c8 --- /dev/null +++ b/pkg/util/exec_windows.go @@ -0,0 +1,81 @@ +/* +Copyright 2021 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" +) + +// Exec creates a new process with the specified arguments. +func Exec(name string, arg ...string) *exec.Cmd { + // Windows does not handle relative path names in exec very well. + name = filepath.Clean(name) + cmdArgs := arg + + // Detect scripts via file extension and automatically invoke them within a shell. + // This mirrors the Linux behavior if the execute bit on file where a shell context + // is automatically created. + switch strings.ToLower(filepath.Ext(name)) { + // Batch Scripts + case ".cmd", ".bat": + cmdArgs = append([]string{"/C", name}, cmdArgs...) + name = "cmd.exe" + // Powershell Scripts + case ".ps1": + cmdArgs = append([]string{"-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "RemoteSigned", name}, cmdArgs...) + name = "powershell.exe" + default: + // Run directly. + } + + return exec.Command(name, cmdArgs...) +} + +// ExitStatus returns the exit code of the application. +func ExitStatus(cmd *exec.Cmd) int { + return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() +} + +// Kill the process and subprocesses. +func Kill(cmd *exec.Cmd) error { + if cmd.Process == nil { + return fmt.Errorf("%v does not have a process handle", cmd) + } + + // Use taskkill to kill the child process by process id. + // /F = Force + // /T = Kill child processes. + // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/taskkill + kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid)) + kill.Stderr = os.Stderr + kill.Stdout = os.Stdout + err := kill.Run() + if execErr, ok := err.(*exec.ExitError); ok { + // Error code 128 (ERROR_WAIT_NO_CHILDREN) means that taskkill couldn't find the process, it probably died already. + // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- + if execErr.ExitCode() == 128 { + return nil + } + } + return err +} diff --git a/pkg/util/metrics/helpers.go b/pkg/util/metrics/helpers.go index 5e1d79c51..58037a6f2 100644 --- a/pkg/util/metrics/helpers.go +++ b/pkg/util/metrics/helpers.go @@ -71,6 +71,7 @@ func ParsePrometheusMetrics(metricsText string) ([]Float64MetricRepresentation, var metrics []Float64MetricRepresentation var textParser expfmt.TextParser + metricsText = strings.ReplaceAll(metricsText, "\r", "") metricFamilies, err := textParser.TextToMetricFamilies(strings.NewReader(metricsText)) if err != nil { return metrics, err diff --git a/pkg/util/metrics/helpers_test.go b/pkg/util/metrics/helpers_test.go index 80625cbea..8be27bf00 100644 --- a/pkg/util/metrics/helpers_test.go +++ b/pkg/util/metrics/helpers_test.go @@ -63,7 +63,7 @@ func TestPrometheusMetricsParsingAndMatching(t *testing.T) { Name: "host_uptime", Labels: map[string]string{"kernel_version": "mismatched-version"}, }, - // Non-exsistant metric. + // Non-existant metric. { Name: "host_downtime", Labels: map[string]string{}, @@ -109,7 +109,7 @@ func TestPrometheusMetricsParsingAndMatching(t *testing.T) { Name: "host_uptime", Labels: map[string]string{"kernel_version": "mismatched-version"}, }, - // Non-exsistant metric. + // Non-existant metric. { Name: "host_downtime", Labels: map[string]string{}, diff --git a/pkg/util/metrics/testdata/hello-world.bat b/pkg/util/metrics/testdata/hello-world.bat new file mode 100644 index 000000000..f6e3e983f --- /dev/null +++ b/pkg/util/metrics/testdata/hello-world.bat @@ -0,0 +1,2 @@ +@echo off +echo Hello World diff --git a/pkg/util/metrics/testdata/hello-world.cmd b/pkg/util/metrics/testdata/hello-world.cmd new file mode 100644 index 000000000..f6e3e983f --- /dev/null +++ b/pkg/util/metrics/testdata/hello-world.cmd @@ -0,0 +1,2 @@ +@echo off +echo Hello World diff --git a/pkg/util/metrics/testdata/hello-world.ps1 b/pkg/util/metrics/testdata/hello-world.ps1 new file mode 100644 index 000000000..87e8f9786 --- /dev/null +++ b/pkg/util/metrics/testdata/hello-world.ps1 @@ -0,0 +1 @@ +Write-Host "Hello World"