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 }