Files
nomad/command/agent/monitor/monitor_test.go
tehut 21841d3067 Add historical journald and log export flags to operator debug command (#26410)
* Add -log-file-export and -log-lookback commands to add historical log to
debug capture
* use monitor.PrepFile() helper for other historical log tests
2025-08-04 13:55:25 -07:00

189 lines
3.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package monitor
import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/ci"
"github.com/shoenig/test/must"
"github.com/stretchr/testify/require"
)
func TestMonitor_Start(t *testing.T) {
ci.Parallel(t)
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Error,
})
m := New(512, logger, &log.LoggerOptions{
Level: log.Debug,
})
logCh := m.Start()
defer m.Stop()
go func() {
logger.Debug("test log")
time.Sleep(10 * time.Millisecond)
}()
for {
select {
case log := <-logCh:
require.Contains(t, string(log), "[DEBUG] test log")
return
case <-time.After(3 * time.Second):
t.Fatal("Expected to receive from log channel")
}
}
}
// Ensure number of dropped messages are logged
func TestMonitor_DroppedMessages(t *testing.T) {
ci.Parallel(t)
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Warn,
})
m := new(5, logger, &log.LoggerOptions{
Level: log.Debug,
})
m.droppedDuration = 5 * time.Millisecond
doneCh := make(chan struct{})
defer close(doneCh)
logCh := m.Start()
for i := 0; i <= 100; i++ {
logger.Debug(fmt.Sprintf("test message %d", i))
}
received := ""
passed := make(chan struct{})
go func() {
for {
select {
case recv := <-logCh:
received += string(recv)
if strings.Contains(received, "[WARN] Monitor dropped") {
close(passed)
}
}
}
}()
TEST:
for {
select {
case <-passed:
break TEST
case <-time.After(2 * time.Second):
require.Fail(t, "expected to see warn dropped messages")
}
}
}
func TestMonitor_Export(t *testing.T) {
ci.Parallel(t)
const (
expectedText = "log log log log log"
)
goldenFilePath := PrepFile(t).Name()
goldenFileContents, err := os.ReadFile(goldenFilePath)
must.NoError(t, err)
testFile, err := os.CreateTemp("", "nomadtest")
must.NoError(t, err)
_, err = testFile.Write([]byte(expectedText))
must.NoError(t, err)
inlineFilePath := testFile.Name()
logger := log.NewInterceptLogger(&log.LoggerOptions{
Level: log.Error,
})
ctx, cancel := context.WithCancel(context.Background())
cases := []struct {
name string
opts MonitorExportOpts
expected string
expectClose bool
}{
{
name: "happy_path_logpath_long_file",
opts: MonitorExportOpts{
Context: ctx,
Logger: logger,
OnDisk: true,
NomadLogPath: goldenFilePath,
},
expected: string(goldenFileContents),
},
{
name: "happy_path_logpath_short_file",
opts: MonitorExportOpts{
Context: ctx,
Logger: logger,
OnDisk: true,
NomadLogPath: inlineFilePath,
},
expected: expectedText,
},
{
name: "close client context",
opts: MonitorExportOpts{
Context: ctx,
Logger: logger,
OnDisk: true,
NomadLogPath: inlineFilePath,
},
expected: expectedText,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
monitor, err := NewExportMonitor(tc.opts)
must.NoError(t, err)
logCh := monitor.Start()
if tc.expectClose {
cancel()
}
var builder strings.Builder
TEST:
for {
select {
case log, ok := <-logCh:
if !ok {
break TEST
}
builder.Write(log)
default:
continue
}
}
if !tc.expectClose {
must.Eq(t, strings.TrimSpace(tc.expected), strings.TrimSpace(builder.String()))
} else {
must.Eq(t, builder.String(), "")
}
})
}
}