github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/logmon/logmon_test.go (about)

     1  package logmon
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"testing"
    11  
    12  	"github.com/hashicorp/nomad/ci"
    13  	"github.com/hashicorp/nomad/client/lib/fifo"
    14  	"github.com/hashicorp/nomad/helper/testlog"
    15  	"github.com/hashicorp/nomad/helper/uuid"
    16  	"github.com/hashicorp/nomad/testutil"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestLogmon_Start_rotate(t *testing.T) {
    21  	ci.Parallel(t)
    22  
    23  	require := require.New(t)
    24  	var stdoutFifoPath, stderrFifoPath string
    25  
    26  	dir := t.TempDir()
    27  
    28  	if runtime.GOOS == "windows" {
    29  		stdoutFifoPath = "//./pipe/test-rotate.stdout"
    30  		stderrFifoPath = "//./pipe/test-rotate.stderr"
    31  	} else {
    32  		stdoutFifoPath = filepath.Join(dir, "stdout.fifo")
    33  		stderrFifoPath = filepath.Join(dir, "stderr.fifo")
    34  	}
    35  
    36  	cfg := &LogConfig{
    37  		LogDir:        dir,
    38  		StdoutLogFile: "stdout",
    39  		StdoutFifo:    stdoutFifoPath,
    40  		StderrLogFile: "stderr",
    41  		StderrFifo:    stderrFifoPath,
    42  		MaxFiles:      2,
    43  		MaxFileSizeMB: 1,
    44  	}
    45  
    46  	lm := NewLogMon(testlog.HCLogger(t))
    47  	require.NoError(lm.Start(cfg))
    48  
    49  	stdout, err := fifo.OpenWriter(stdoutFifoPath)
    50  	require.NoError(err)
    51  
    52  	// Write enough bytes such that the log is rotated
    53  	bytes1MB := make([]byte, 1024*1024)
    54  	_, err = rand.Read(bytes1MB)
    55  	require.NoError(err)
    56  
    57  	_, err = stdout.Write(bytes1MB)
    58  	require.NoError(err)
    59  
    60  	testutil.WaitForResult(func() (bool, error) {
    61  		_, err = os.Stat(filepath.Join(dir, "stdout.0"))
    62  		return err == nil, err
    63  	}, func(err error) {
    64  		require.NoError(err)
    65  	})
    66  	testutil.WaitForResult(func() (bool, error) {
    67  		_, err = os.Stat(filepath.Join(dir, "stdout.1"))
    68  		return err == nil, err
    69  	}, func(err error) {
    70  		require.NoError(err)
    71  	})
    72  	_, err = os.Stat(filepath.Join(dir, "stdout.2"))
    73  	require.Error(err)
    74  	require.NoError(lm.Stop())
    75  	require.NoError(lm.Stop())
    76  }
    77  
    78  // asserts that calling Start twice restarts the log rotator and that any logs
    79  // published while the listener was unavailable are received.
    80  func TestLogmon_Start_restart_flusheslogs(t *testing.T) {
    81  	ci.Parallel(t)
    82  
    83  	if runtime.GOOS == "windows" {
    84  		t.Skip("windows does not support pushing data to a pipe with no servers")
    85  	}
    86  
    87  	require := require.New(t)
    88  	var stdoutFifoPath, stderrFifoPath string
    89  
    90  	dir := t.TempDir()
    91  
    92  	if runtime.GOOS == "windows" {
    93  		stdoutFifoPath = "//./pipe/test-restart.stdout"
    94  		stderrFifoPath = "//./pipe/test-restart.stderr"
    95  	} else {
    96  		stdoutFifoPath = filepath.Join(dir, "stdout.fifo")
    97  		stderrFifoPath = filepath.Join(dir, "stderr.fifo")
    98  	}
    99  
   100  	cfg := &LogConfig{
   101  		LogDir:        dir,
   102  		StdoutLogFile: "stdout",
   103  		StdoutFifo:    stdoutFifoPath,
   104  		StderrLogFile: "stderr",
   105  		StderrFifo:    stderrFifoPath,
   106  		MaxFiles:      2,
   107  		MaxFileSizeMB: 1,
   108  	}
   109  
   110  	lm := NewLogMon(testlog.HCLogger(t))
   111  	impl, ok := lm.(*logmonImpl)
   112  	require.True(ok)
   113  	require.NoError(lm.Start(cfg))
   114  
   115  	stdout, err := fifo.OpenWriter(stdoutFifoPath)
   116  	require.NoError(err)
   117  	stderr, err := fifo.OpenWriter(stderrFifoPath)
   118  	require.NoError(err)
   119  
   120  	// Write a string and assert it was written to the file
   121  	_, err = stdout.Write([]byte("test\n"))
   122  	require.NoError(err)
   123  
   124  	testutil.WaitForResult(func() (bool, error) {
   125  		raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0"))
   126  		if err != nil {
   127  			return false, err
   128  		}
   129  		return "test\n" == string(raw), fmt.Errorf("unexpected stdout %q", string(raw))
   130  	}, func(err error) {
   131  		require.NoError(err)
   132  	})
   133  	require.True(impl.tl.IsRunning())
   134  
   135  	// Close stdout and assert that logmon no longer writes to the file
   136  	require.NoError(stdout.Close())
   137  	require.NoError(stderr.Close())
   138  
   139  	testutil.WaitForResult(func() (bool, error) {
   140  		return !impl.tl.IsRunning(), fmt.Errorf("logmon is still running")
   141  	}, func(err error) {
   142  		require.NoError(err)
   143  	})
   144  
   145  	stdout, err = fifo.OpenWriter(stdoutFifoPath)
   146  	require.NoError(err)
   147  	stderr, err = fifo.OpenWriter(stderrFifoPath)
   148  	require.NoError(err)
   149  
   150  	_, err = stdout.Write([]byte("te"))
   151  	require.NoError(err)
   152  
   153  	testutil.WaitForResult(func() (bool, error) {
   154  		raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0"))
   155  		if err != nil {
   156  			return false, err
   157  		}
   158  		return "test\n" == string(raw), fmt.Errorf("unexpected stdout %q", string(raw))
   159  	}, func(err error) {
   160  		require.NoError(err)
   161  	})
   162  
   163  	// Start logmon again and assert that it appended to the file
   164  	require.NoError(lm.Start(cfg))
   165  
   166  	stdout, err = fifo.OpenWriter(stdoutFifoPath)
   167  	require.NoError(err)
   168  	stderr, err = fifo.OpenWriter(stderrFifoPath)
   169  	require.NoError(err)
   170  
   171  	_, err = stdout.Write([]byte("st\n"))
   172  	require.NoError(err)
   173  	testutil.WaitForResult(func() (bool, error) {
   174  		raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0"))
   175  		if err != nil {
   176  			return false, err
   177  		}
   178  
   179  		expected := "test\ntest\n" == string(raw)
   180  		return expected, fmt.Errorf("unexpected stdout %q", string(raw))
   181  	}, func(err error) {
   182  		require.NoError(err)
   183  	})
   184  }
   185  
   186  // asserts that calling Start twice restarts the log rotator
   187  func TestLogmon_Start_restart(t *testing.T) {
   188  	ci.Parallel(t)
   189  
   190  	require := require.New(t)
   191  	var stdoutFifoPath, stderrFifoPath string
   192  
   193  	dir := t.TempDir()
   194  
   195  	if runtime.GOOS == "windows" {
   196  		stdoutFifoPath = "//./pipe/test-restart.stdout"
   197  		stderrFifoPath = "//./pipe/test-restart.stderr"
   198  	} else {
   199  		stdoutFifoPath = filepath.Join(dir, "stdout.fifo")
   200  		stderrFifoPath = filepath.Join(dir, "stderr.fifo")
   201  	}
   202  
   203  	cfg := &LogConfig{
   204  		LogDir:        dir,
   205  		StdoutLogFile: "stdout",
   206  		StdoutFifo:    stdoutFifoPath,
   207  		StderrLogFile: "stderr",
   208  		StderrFifo:    stderrFifoPath,
   209  		MaxFiles:      2,
   210  		MaxFileSizeMB: 1,
   211  	}
   212  
   213  	lm := NewLogMon(testlog.HCLogger(t))
   214  	impl, ok := lm.(*logmonImpl)
   215  	require.True(ok)
   216  	require.NoError(lm.Start(cfg))
   217  	t.Cleanup(func() {
   218  		require.NoError(lm.Stop())
   219  	})
   220  
   221  	stdout, err := fifo.OpenWriter(stdoutFifoPath)
   222  	require.NoError(err)
   223  	stderr, err := fifo.OpenWriter(stderrFifoPath)
   224  	require.NoError(err)
   225  
   226  	// Write a string and assert it was written to the file
   227  	_, err = stdout.Write([]byte("test\n"))
   228  	require.NoError(err)
   229  
   230  	testutil.WaitForResult(func() (bool, error) {
   231  		raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0"))
   232  		if err != nil {
   233  			return false, err
   234  		}
   235  		return "test\n" == string(raw), fmt.Errorf("unexpected stdout %q", string(raw))
   236  	}, func(err error) {
   237  		require.NoError(err)
   238  	})
   239  	require.True(impl.tl.IsRunning())
   240  
   241  	// Close stderr and assert that logmon no longer writes to the file
   242  	// Keep stdout open to ensure that IsRunning requires both
   243  	require.NoError(stderr.Close())
   244  
   245  	testutil.WaitForResult(func() (bool, error) {
   246  		return !impl.tl.IsRunning(), fmt.Errorf("logmon is still running")
   247  	}, func(err error) {
   248  		require.NoError(err)
   249  	})
   250  
   251  	// Start logmon again and assert that it can receive logs again
   252  	require.NoError(lm.Start(cfg))
   253  
   254  	stdout, err = fifo.OpenWriter(stdoutFifoPath)
   255  	require.NoError(err)
   256  	t.Cleanup(func() {
   257  		require.NoError(stdout.Close())
   258  	})
   259  
   260  	stderr, err = fifo.OpenWriter(stderrFifoPath)
   261  	require.NoError(err)
   262  	t.Cleanup(func() {
   263  		require.NoError(stderr.Close())
   264  	})
   265  
   266  	_, err = stdout.Write([]byte("test\n"))
   267  	require.NoError(err)
   268  	testutil.WaitForResult(func() (bool, error) {
   269  		raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0"))
   270  		if err != nil {
   271  			return false, err
   272  		}
   273  
   274  		expected := "test\ntest\n" == string(raw)
   275  		return expected, fmt.Errorf("unexpected stdout %q", string(raw))
   276  	}, func(err error) {
   277  		require.NoError(err)
   278  	})
   279  }
   280  
   281  // panicWriter panics on use
   282  type panicWriter struct{}
   283  
   284  func (panicWriter) Write([]byte) (int, error) {
   285  	panic("should not be called")
   286  }
   287  func (panicWriter) Close() error {
   288  	panic("should not be called")
   289  }
   290  
   291  // TestLogmon_NewError asserts that newLogRotatorWrapper will return an error
   292  // if its unable to create the necessray files.
   293  func TestLogmon_NewError(t *testing.T) {
   294  	ci.Parallel(t)
   295  
   296  	// Pick a path that does not exist
   297  	path := filepath.Join(uuid.Generate(), uuid.Generate(), uuid.Generate())
   298  
   299  	logger := testlog.HCLogger(t)
   300  
   301  	// No code that uses the writer should get hit
   302  	rotator := panicWriter{}
   303  
   304  	w, err := newLogRotatorWrapper(path, logger, rotator)
   305  	require.Error(t, err)
   306  	require.Nil(t, w)
   307  }