github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/debuglog_db_internal_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 corelogger "github.com/juju/juju/core/logger" 17 "github.com/juju/juju/rpc/params" 18 "github.com/juju/juju/state" 19 coretesting "github.com/juju/juju/testing" 20 ) 21 22 type debugLogDBIntSuite struct { 23 coretesting.BaseSuite 24 sock *fakeDebugLogSocket 25 clock *testclock.Clock 26 timeout time.Duration 27 } 28 29 var _ = gc.Suite(&debugLogDBIntSuite{}) 30 31 func (s *debugLogDBIntSuite) SetUpTest(c *gc.C) { 32 s.BaseSuite.SetUpTest(c) 33 s.sock = newFakeDebugLogSocket() 34 s.clock = testclock.NewClock(time.Now()) 35 s.timeout = time.Minute 36 } 37 38 func (s *debugLogDBIntSuite) TestParamConversion(c *gc.C) { 39 t1 := time.Date(2016, 11, 30, 10, 51, 0, 0, time.UTC) 40 reqParams := debugLogParams{ 41 fromTheStart: false, 42 noTail: true, 43 backlog: 11, 44 startTime: t1, 45 filterLevel: loggo.INFO, 46 includeEntity: []string{"foo"}, 47 includeModule: []string{"bar"}, 48 includeLabel: []string{"xxx"}, 49 excludeEntity: []string{"baz"}, 50 excludeModule: []string{"qux"}, 51 excludeLabel: []string{"yyy"}, 52 } 53 54 called := false 55 s.PatchValue(&newLogTailer, func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 56 called = true 57 58 // Start time will be used once the client is extended to send 59 // time range arguments. 60 c.Assert(params.StartTime, gc.Equals, t1) 61 c.Assert(params.NoTail, jc.IsTrue) 62 c.Assert(params.MinLevel, gc.Equals, loggo.INFO) 63 c.Assert(params.InitialLines, gc.Equals, 11) 64 c.Assert(params.IncludeEntity, jc.DeepEquals, []string{"foo"}) 65 c.Assert(params.IncludeModule, jc.DeepEquals, []string{"bar"}) 66 c.Assert(params.IncludeLabel, jc.DeepEquals, []string{"xxx"}) 67 c.Assert(params.ExcludeEntity, jc.DeepEquals, []string{"baz"}) 68 c.Assert(params.ExcludeModule, jc.DeepEquals, []string{"qux"}) 69 c.Assert(params.ExcludeLabel, jc.DeepEquals, []string{"yyy"}) 70 71 return newFakeLogTailer(), nil 72 }) 73 74 stop := make(chan struct{}) 75 close(stop) // Stop the request immediately. 76 err := handleDebugLogDBRequest(s.clock, s.timeout, nil, reqParams, s.sock, stop) 77 c.Assert(err, jc.ErrorIsNil) 78 c.Assert(called, jc.IsTrue) 79 } 80 81 func (s *debugLogDBIntSuite) TestParamConversionReplay(c *gc.C) { 82 reqParams := debugLogParams{ 83 fromTheStart: true, 84 backlog: 123, 85 } 86 87 called := false 88 s.PatchValue(&newLogTailer, func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 89 called = true 90 91 c.Assert(params.StartTime.IsZero(), jc.IsTrue) 92 c.Assert(params.InitialLines, gc.Equals, 0) 93 94 return newFakeLogTailer(), nil 95 }) 96 97 stop := make(chan struct{}) 98 close(stop) // Stop the request immediately. 99 err := handleDebugLogDBRequest(s.clock, s.timeout, nil, reqParams, s.sock, stop) 100 c.Assert(err, jc.ErrorIsNil) 101 c.Assert(called, jc.IsTrue) 102 } 103 104 func (s *debugLogDBIntSuite) TestFullRequest(c *gc.C) { 105 // Set up a fake log tailer with a 2 log records ready to send. 106 tailer := newFakeLogTailer() 107 tailer.logsCh <- &corelogger.LogRecord{ 108 Time: time.Date(2015, 6, 19, 15, 34, 37, 0, time.UTC), 109 Entity: "machine-99", 110 Module: "some.where", 111 Location: "code.go:42", 112 Level: loggo.INFO, 113 Message: "stuff happened", 114 } 115 tailer.logsCh <- &corelogger.LogRecord{ 116 Time: time.Date(2015, 6, 19, 15, 36, 40, 0, time.UTC), 117 Entity: "unit-foo-2", 118 Module: "else.where", 119 Location: "go.go:22", 120 Level: loggo.ERROR, 121 Message: "whoops", 122 } 123 s.PatchValue( 124 &newLogTailer, 125 func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 126 return tailer, nil 127 }, 128 ) 129 130 stop := make(chan struct{}) 131 done := s.runRequest(debugLogParams{}, stop) 132 133 s.assertOutput(c, []string{ 134 "ok", // sendOk() call needs to happen first. 135 "machine-99: 2015-06-19 15:34:37 INFO some.where code.go:42 stuff happened\n", 136 "unit-foo-2: 2015-06-19 15:36:40 ERROR else.where go.go:22 whoops\n", 137 }) 138 139 // Check the request stops when requested. 140 close(stop) 141 s.assertStops(c, done, tailer) 142 } 143 144 func (s *debugLogDBIntSuite) TestTimeout(c *gc.C) { 145 // Set up a fake log tailer with a 2 log records ready to send. 146 tailer := newFakeLogTailer() 147 tailer.logsCh <- &corelogger.LogRecord{ 148 Time: time.Date(2015, 6, 19, 15, 34, 37, 0, time.UTC), 149 Entity: "machine-99", 150 Module: "some.where", 151 Location: "code.go:42", 152 Level: loggo.INFO, 153 Message: "stuff happened", 154 } 155 tailer.logsCh <- &corelogger.LogRecord{ 156 Time: time.Date(2015, 6, 19, 15, 36, 40, 0, time.UTC), 157 Entity: "unit-foo-2", 158 Module: "else.where", 159 Location: "go.go:22", 160 Level: loggo.ERROR, 161 Message: "whoops", 162 } 163 s.PatchValue(&newLogTailer, func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 164 return tailer, nil 165 }) 166 167 stop := make(chan struct{}) 168 done := s.runRequest(debugLogParams{}, stop) 169 170 s.assertOutput(c, []string{ 171 "ok", // sendOk() call needs to happen first. 172 "machine-99: 2015-06-19 15:34:37 INFO some.where code.go:42 stuff happened\n", 173 "unit-foo-2: 2015-06-19 15:36:40 ERROR else.where go.go:22 whoops\n", 174 }) 175 176 s.assertRunning(c, done, tailer) 177 s.clock.Advance(s.timeout) 178 179 // Check the request stops when requested. 180 s.assertStops(c, done, tailer) 181 } 182 183 func (s *debugLogDBIntSuite) TestRequestStopsWhenTailerStops(c *gc.C) { 184 tailer := newFakeLogTailer() 185 s.PatchValue(&newLogTailer, func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 186 close(tailer.logsCh) // make the request stop immediately 187 return tailer, nil 188 }) 189 190 err := handleDebugLogDBRequest(s.clock, s.timeout, nil, debugLogParams{}, s.sock, nil) 191 c.Assert(err, jc.ErrorIsNil) 192 c.Assert(tailer.stopped, jc.IsTrue) 193 } 194 195 func (s *debugLogDBIntSuite) TestMaxLines(c *gc.C) { 196 // Set up a fake log tailer with a 5 log records ready to send. 197 tailer := newFakeLogTailer() 198 for i := 0; i < 5; i++ { 199 tailer.logsCh <- &corelogger.LogRecord{ 200 Time: time.Date(2015, 6, 19, 15, 34, 37, 0, time.UTC), 201 Entity: "machine-99", 202 Module: "some.where", 203 Location: "code.go:42", 204 Level: loggo.INFO, 205 Message: "stuff happened", 206 } 207 } 208 s.PatchValue(&newLogTailer, func(_ state.LogTailerState, params corelogger.LogTailerParams) (corelogger.LogTailer, error) { 209 return tailer, nil 210 }) 211 212 done := s.runRequest(debugLogParams{maxLines: 3}, nil) 213 214 s.assertOutput(c, []string{ 215 "ok", // sendOk() call needs to happen first. 216 "machine-99: 2015-06-19 15:34:37 INFO some.where code.go:42 stuff happened\n", 217 "machine-99: 2015-06-19 15:34:37 INFO some.where code.go:42 stuff happened\n", 218 "machine-99: 2015-06-19 15:34:37 INFO some.where code.go:42 stuff happened\n", 219 }) 220 221 // The tailer should now stop by itself after the line limit was reached. 222 s.assertStops(c, done, tailer) 223 } 224 225 func (s *debugLogDBIntSuite) runRequest(params debugLogParams, stop chan struct{}) chan error { 226 done := make(chan error) 227 go func() { 228 done <- handleDebugLogDBRequest(s.clock, s.timeout, &fakeState{}, params, s.sock, stop) 229 }() 230 return done 231 } 232 233 func (s *debugLogDBIntSuite) assertOutput(c *gc.C, expectedWrites []string) { 234 timeout := time.After(coretesting.LongWait) 235 for i, expectedWrite := range expectedWrites { 236 select { 237 case actualWrite := <-s.sock.writes: 238 c.Assert(actualWrite, gc.Equals, expectedWrite) 239 case <-timeout: 240 c.Errorf("timed out waiting for socket write (received %d)", i) 241 } 242 } 243 } 244 245 func (s *debugLogDBIntSuite) assertStops(c *gc.C, done chan error, tailer *fakeLogTailer) { 246 select { 247 case err := <-done: 248 c.Assert(err, jc.ErrorIsNil) 249 c.Assert(tailer.stopped, jc.IsTrue) 250 case <-time.After(coretesting.LongWait): 251 c.Error("timed out waiting for request handler to stop") 252 } 253 } 254 255 func (s *debugLogDBIntSuite) assertRunning(c *gc.C, done chan error, tailer *fakeLogTailer) { 256 select { 257 case err := <-done: 258 c.Errorf("unexpected exit, %v", errors.ErrorStack(err)) 259 case <-time.After(coretesting.ShortWait): 260 c.Assert(tailer.stopped, jc.IsFalse) 261 } 262 } 263 264 type fakeState struct { 265 state.LogTailerState 266 } 267 268 func newFakeLogTailer() *fakeLogTailer { 269 return &fakeLogTailer{ 270 logsCh: make(chan *corelogger.LogRecord, 10), 271 } 272 } 273 274 type fakeLogTailer struct { 275 corelogger.LogTailer 276 logsCh chan *corelogger.LogRecord 277 stopped bool 278 } 279 280 func (t *fakeLogTailer) Logs() <-chan *corelogger.LogRecord { 281 return t.logsCh 282 } 283 284 func (t *fakeLogTailer) Stop() error { 285 t.stopped = true 286 return nil 287 } 288 289 func (t *fakeLogTailer) Err() error { 290 return nil 291 } 292 293 func newFakeDebugLogSocket() *fakeDebugLogSocket { 294 return &fakeDebugLogSocket{ 295 writes: make(chan string, 10), 296 } 297 } 298 299 type fakeDebugLogSocket struct { 300 writes chan string 301 } 302 303 func (s *fakeDebugLogSocket) sendOk() { 304 s.writes <- "ok" 305 } 306 307 func (s *fakeDebugLogSocket) sendError(err error) { 308 s.writes <- fmt.Sprintf("err: %v", err) 309 } 310 311 func (s *fakeDebugLogSocket) sendLogRecord(r *params.LogMessage) error { 312 s.writes <- fmt.Sprintf("%s: %s %s %s %s %s\n", 313 r.Entity, 314 s.formatTime(r.Timestamp), 315 r.Severity, 316 r.Module, 317 r.Location, 318 r.Message) 319 return nil 320 } 321 322 func (c *fakeDebugLogSocket) formatTime(t time.Time) string { 323 return t.In(time.UTC).Format("2006-01-02 15:04:05") 324 }