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  }