github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/debuglog_internal_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // This is an internal package test.
     5  
     6  package apiserver
     7  
     8  import (
     9  	"bytes"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"time"
    14  
    15  	"github.com/juju/loggo"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "launchpad.net/gocheck"
    18  
    19  	"github.com/juju/juju/testing"
    20  )
    21  
    22  type debugInternalSuite struct {
    23  	testing.BaseSuite
    24  }
    25  
    26  var _ = gc.Suite(&debugInternalSuite{})
    27  
    28  func (s *debugInternalSuite) TestParseLogLine(c *gc.C) {
    29  	line := "machine-0: 2014-03-24 22:34:25 INFO juju.cmd.jujud machine.go:127 machine agent machine-0 start (1.17.7.1-trusty-amd64 [gc])"
    30  	logLine := parseLogLine(line)
    31  	c.Assert(logLine.line, gc.Equals, line)
    32  	c.Assert(logLine.agent, gc.Equals, "machine-0")
    33  	c.Assert(logLine.level, gc.Equals, loggo.INFO)
    34  	c.Assert(logLine.module, gc.Equals, "juju.cmd.jujud")
    35  }
    36  
    37  func (s *debugInternalSuite) TestParseLogLineMachineMultiline(c *gc.C) {
    38  	line := "machine-1: continuation line"
    39  	logLine := parseLogLine(line)
    40  	c.Assert(logLine.line, gc.Equals, line)
    41  	c.Assert(logLine.agent, gc.Equals, "machine-1")
    42  	c.Assert(logLine.level, gc.Equals, loggo.UNSPECIFIED)
    43  	c.Assert(logLine.module, gc.Equals, "")
    44  }
    45  
    46  func (s *debugInternalSuite) TestParseLogLineInvalid(c *gc.C) {
    47  	line := "not a full line"
    48  	logLine := parseLogLine(line)
    49  	c.Assert(logLine.line, gc.Equals, line)
    50  	c.Assert(logLine.agent, gc.Equals, "")
    51  	c.Assert(logLine.level, gc.Equals, loggo.UNSPECIFIED)
    52  	c.Assert(logLine.module, gc.Equals, "")
    53  }
    54  
    55  func checkLevel(logValue, streamValue loggo.Level) bool {
    56  	stream := &logStream{}
    57  	if streamValue != loggo.UNSPECIFIED {
    58  		stream.filterLevel = streamValue
    59  	}
    60  	line := &logLine{level: logValue}
    61  	return stream.checkLevel(line)
    62  }
    63  
    64  func (s *debugInternalSuite) TestCheckLevel(c *gc.C) {
    65  	c.Check(checkLevel(loggo.UNSPECIFIED, loggo.UNSPECIFIED), jc.IsTrue)
    66  	c.Check(checkLevel(loggo.TRACE, loggo.UNSPECIFIED), jc.IsTrue)
    67  	c.Check(checkLevel(loggo.DEBUG, loggo.UNSPECIFIED), jc.IsTrue)
    68  	c.Check(checkLevel(loggo.INFO, loggo.UNSPECIFIED), jc.IsTrue)
    69  	c.Check(checkLevel(loggo.WARNING, loggo.UNSPECIFIED), jc.IsTrue)
    70  	c.Check(checkLevel(loggo.ERROR, loggo.UNSPECIFIED), jc.IsTrue)
    71  	c.Check(checkLevel(loggo.CRITICAL, loggo.UNSPECIFIED), jc.IsTrue)
    72  
    73  	c.Check(checkLevel(loggo.UNSPECIFIED, loggo.TRACE), jc.IsFalse)
    74  	c.Check(checkLevel(loggo.TRACE, loggo.TRACE), jc.IsTrue)
    75  	c.Check(checkLevel(loggo.DEBUG, loggo.TRACE), jc.IsTrue)
    76  	c.Check(checkLevel(loggo.INFO, loggo.TRACE), jc.IsTrue)
    77  	c.Check(checkLevel(loggo.WARNING, loggo.TRACE), jc.IsTrue)
    78  	c.Check(checkLevel(loggo.ERROR, loggo.TRACE), jc.IsTrue)
    79  	c.Check(checkLevel(loggo.CRITICAL, loggo.TRACE), jc.IsTrue)
    80  
    81  	c.Check(checkLevel(loggo.UNSPECIFIED, loggo.INFO), jc.IsFalse)
    82  	c.Check(checkLevel(loggo.TRACE, loggo.INFO), jc.IsFalse)
    83  	c.Check(checkLevel(loggo.DEBUG, loggo.INFO), jc.IsFalse)
    84  	c.Check(checkLevel(loggo.INFO, loggo.INFO), jc.IsTrue)
    85  	c.Check(checkLevel(loggo.WARNING, loggo.INFO), jc.IsTrue)
    86  	c.Check(checkLevel(loggo.ERROR, loggo.INFO), jc.IsTrue)
    87  	c.Check(checkLevel(loggo.CRITICAL, loggo.INFO), jc.IsTrue)
    88  }
    89  
    90  func checkIncludeEntity(logValue string, agent ...string) bool {
    91  	stream := &logStream{includeEntity: agent}
    92  	line := &logLine{agent: logValue}
    93  	return stream.checkIncludeEntity(line)
    94  }
    95  
    96  func (s *debugInternalSuite) TestCheckIncludeEntity(c *gc.C) {
    97  	c.Check(checkIncludeEntity("machine-0"), jc.IsTrue)
    98  	c.Check(checkIncludeEntity("machine-0", "machine-0"), jc.IsTrue)
    99  	c.Check(checkIncludeEntity("machine-1", "machine-0"), jc.IsFalse)
   100  	c.Check(checkIncludeEntity("machine-1", "machine-0", "machine-1"), jc.IsTrue)
   101  	c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0"), jc.IsFalse)
   102  	c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0*"), jc.IsTrue)
   103  	c.Check(checkIncludeEntity("machine-0-lxc-0", "machine-0-lxc-*"), jc.IsTrue)
   104  }
   105  
   106  func checkIncludeModule(logValue string, module ...string) bool {
   107  	stream := &logStream{includeModule: module}
   108  	line := &logLine{module: logValue}
   109  	return stream.checkIncludeModule(line)
   110  }
   111  
   112  func (s *debugInternalSuite) TestCheckIncludeModule(c *gc.C) {
   113  	c.Check(checkIncludeModule("juju"), jc.IsTrue)
   114  	c.Check(checkIncludeModule("juju", "juju"), jc.IsTrue)
   115  	c.Check(checkIncludeModule("juju", "juju.environ"), jc.IsFalse)
   116  	c.Check(checkIncludeModule("juju.provisioner", "juju"), jc.IsTrue)
   117  	c.Check(checkIncludeModule("juju.provisioner", "juju*"), jc.IsFalse)
   118  	c.Check(checkIncludeModule("juju.provisioner", "juju.environ"), jc.IsFalse)
   119  	c.Check(checkIncludeModule("unit.mysql/1", "juju", "unit"), jc.IsTrue)
   120  }
   121  
   122  func checkExcludeEntity(logValue string, agent ...string) bool {
   123  	stream := &logStream{excludeEntity: agent}
   124  	line := &logLine{agent: logValue}
   125  	return stream.exclude(line)
   126  }
   127  
   128  func (s *debugInternalSuite) TestCheckExcludeEntity(c *gc.C) {
   129  	c.Check(checkExcludeEntity("machine-0"), jc.IsFalse)
   130  	c.Check(checkExcludeEntity("machine-0", "machine-0"), jc.IsTrue)
   131  	c.Check(checkExcludeEntity("machine-1", "machine-0"), jc.IsFalse)
   132  	c.Check(checkExcludeEntity("machine-1", "machine-0", "machine-1"), jc.IsTrue)
   133  	c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0"), jc.IsFalse)
   134  	c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0*"), jc.IsTrue)
   135  	c.Check(checkExcludeEntity("machine-0-lxc-0", "machine-0-lxc-*"), jc.IsTrue)
   136  }
   137  
   138  func checkExcludeModule(logValue string, module ...string) bool {
   139  	stream := &logStream{excludeModule: module}
   140  	line := &logLine{module: logValue}
   141  	return stream.exclude(line)
   142  }
   143  
   144  func (s *debugInternalSuite) TestCheckExcludeModule(c *gc.C) {
   145  	c.Check(checkExcludeModule("juju"), jc.IsFalse)
   146  	c.Check(checkExcludeModule("juju", "juju"), jc.IsTrue)
   147  	c.Check(checkExcludeModule("juju", "juju.environ"), jc.IsFalse)
   148  	c.Check(checkExcludeModule("juju.provisioner", "juju"), jc.IsTrue)
   149  	c.Check(checkExcludeModule("juju.provisioner", "juju*"), jc.IsFalse)
   150  	c.Check(checkExcludeModule("juju.provisioner", "juju.environ"), jc.IsFalse)
   151  	c.Check(checkExcludeModule("unit.mysql/1", "juju", "unit"), jc.IsTrue)
   152  }
   153  
   154  func (s *debugInternalSuite) TestFilterLine(c *gc.C) {
   155  	stream := &logStream{
   156  		filterLevel:   loggo.INFO,
   157  		includeEntity: []string{"machine-0", "unit-mysql*"},
   158  		includeModule: []string{"juju"},
   159  		excludeEntity: []string{"unit-mysql-2"},
   160  		excludeModule: []string{"juju.foo"},
   161  	}
   162  	c.Check(stream.filterLine([]byte(
   163  		"machine-0: date time WARNING juju")), jc.IsTrue)
   164  	c.Check(stream.filterLine([]byte(
   165  		"machine-1: date time WARNING juju")), jc.IsFalse)
   166  	c.Check(stream.filterLine([]byte(
   167  		"unit-mysql-0: date time WARNING juju")), jc.IsTrue)
   168  	c.Check(stream.filterLine([]byte(
   169  		"unit-mysql-1: date time WARNING juju")), jc.IsTrue)
   170  	c.Check(stream.filterLine([]byte(
   171  		"unit-mysql-2: date time WARNING juju")), jc.IsFalse)
   172  	c.Check(stream.filterLine([]byte(
   173  		"unit-wordpress-0: date time WARNING juju")), jc.IsFalse)
   174  	c.Check(stream.filterLine([]byte(
   175  		"machine-0: date time DEBUG juju")), jc.IsFalse)
   176  	c.Check(stream.filterLine([]byte(
   177  		"machine-0: date time WARNING juju.foo.bar")), jc.IsFalse)
   178  }
   179  
   180  func (s *debugInternalSuite) TestCountedFilterLineWithLimit(c *gc.C) {
   181  	stream := &logStream{
   182  		filterLevel: loggo.INFO,
   183  		maxLines:    5,
   184  	}
   185  	line := []byte("machine-0: date time WARNING juju")
   186  	c.Check(stream.countedFilterLine(line), jc.IsTrue)
   187  	c.Check(stream.countedFilterLine(line), jc.IsTrue)
   188  	c.Check(stream.countedFilterLine(line), jc.IsTrue)
   189  	c.Check(stream.countedFilterLine(line), jc.IsTrue)
   190  	c.Check(stream.countedFilterLine(line), jc.IsTrue)
   191  	c.Check(stream.countedFilterLine(line), jc.IsFalse)
   192  	c.Check(stream.countedFilterLine(line), jc.IsFalse)
   193  }
   194  
   195  type chanWriter struct {
   196  	ch chan []byte
   197  }
   198  
   199  func (w *chanWriter) Write(buf []byte) (n int, err error) {
   200  	bufcopy := append([]byte{}, buf...)
   201  	w.ch <- bufcopy
   202  	return len(buf), nil
   203  }
   204  
   205  func (s *debugInternalSuite) testStreamInternal(c *gc.C, fromTheStart bool, backlog, maxLines uint, expected, errMatch string) {
   206  
   207  	dir := c.MkDir()
   208  	logPath := filepath.Join(dir, "logfile.txt")
   209  	logFile, err := os.Create(logPath)
   210  	c.Assert(err, gc.IsNil)
   211  	defer logFile.Close()
   212  	logFileReader, err := os.Open(logPath)
   213  	c.Assert(err, gc.IsNil)
   214  	defer logFileReader.Close()
   215  
   216  	logFile.WriteString(`line 1
   217  line 2
   218  line 3
   219  `)
   220  	stream := &logStream{
   221  		fromTheStart: fromTheStart,
   222  		backlog:      backlog,
   223  		maxLines:     maxLines,
   224  	}
   225  	err = stream.positionLogFile(logFileReader)
   226  	c.Assert(err, gc.IsNil)
   227  	var output bytes.Buffer
   228  	writer := &chanWriter{make(chan []byte)}
   229  	stream.start(logFileReader, writer)
   230  	defer stream.logTailer.Wait()
   231  
   232  	go func() {
   233  		defer stream.tomb.Done()
   234  		stream.tomb.Kill(stream.loop())
   235  	}()
   236  
   237  	logFile.WriteString("line 4\n")
   238  	logFile.WriteString("line 5\n")
   239  
   240  	timeout := time.After(testing.LongWait)
   241  	for output.String() != expected {
   242  		select {
   243  		case buf := <-writer.ch:
   244  			output.Write(buf)
   245  		case <-timeout:
   246  			c.Fatalf("expected data didn't arrive:\n\tobtained: %#v\n\texpected: %#v", output.String(), expected)
   247  		}
   248  	}
   249  
   250  	stream.logTailer.Stop()
   251  
   252  	err = stream.tomb.Wait()
   253  	if errMatch == "" {
   254  		c.Assert(err, gc.IsNil)
   255  	} else {
   256  		c.Assert(err, gc.ErrorMatches, errMatch)
   257  	}
   258  }
   259  
   260  func (s *debugInternalSuite) TestLogStreamLoopFromTheStart(c *gc.C) {
   261  	expected := `line 1
   262  line 2
   263  line 3
   264  line 4
   265  line 5
   266  `
   267  	s.testStreamInternal(c, true, 0, 0, expected, "")
   268  }
   269  
   270  func (s *debugInternalSuite) TestLogStreamLoopFromTheStartMaxLines(c *gc.C) {
   271  	expected := `line 1
   272  line 2
   273  line 3
   274  `
   275  	s.testStreamInternal(c, true, 0, 3, expected, "max lines reached")
   276  }
   277  
   278  func (s *debugInternalSuite) TestLogStreamLoopJustTail(c *gc.C) {
   279  	expected := `line 4
   280  line 5
   281  `
   282  	s.testStreamInternal(c, false, 0, 0, expected, "")
   283  }
   284  
   285  func (s *debugInternalSuite) TestLogStreamLoopBackOneLimitTwo(c *gc.C) {
   286  	expected := `line 3
   287  line 4
   288  `
   289  	s.testStreamInternal(c, false, 1, 2, expected, "max lines reached")
   290  }
   291  
   292  func (s *debugInternalSuite) TestLogStreamLoopTailMaxLinesNotYetReached(c *gc.C) {
   293  	expected := `line 4
   294  line 5
   295  `
   296  	s.testStreamInternal(c, false, 0, 3, expected, "")
   297  }
   298  
   299  func assertStreamParams(c *gc.C, obtained, expected *logStream) {
   300  	c.Check(obtained.includeEntity, jc.DeepEquals, expected.includeEntity)
   301  	c.Check(obtained.includeModule, jc.DeepEquals, expected.includeModule)
   302  	c.Check(obtained.excludeEntity, jc.DeepEquals, expected.excludeEntity)
   303  	c.Check(obtained.excludeModule, jc.DeepEquals, expected.excludeModule)
   304  	c.Check(obtained.maxLines, gc.Equals, expected.maxLines)
   305  	c.Check(obtained.fromTheStart, gc.Equals, expected.fromTheStart)
   306  	c.Check(obtained.filterLevel, gc.Equals, expected.filterLevel)
   307  	c.Check(obtained.backlog, gc.Equals, expected.backlog)
   308  }
   309  
   310  func (s *debugInternalSuite) TestNewLogStream(c *gc.C) {
   311  	obtained, err := newLogStream(nil)
   312  	c.Assert(err, gc.IsNil)
   313  	assertStreamParams(c, obtained, &logStream{})
   314  
   315  	values := url.Values{
   316  		"includeEntity": []string{"machine-1*", "machine-2"},
   317  		"includeModule": []string{"juju", "unit"},
   318  		"excludeEntity": []string{"machine-1-lxc*"},
   319  		"excludeModule": []string{"juju.provisioner"},
   320  		"maxLines":      []string{"300"},
   321  		"backlog":       []string{"100"},
   322  		"level":         []string{"INFO"},
   323  		// OK, just a little nonsense
   324  		"replay": []string{"true"},
   325  	}
   326  	expected := &logStream{
   327  		includeEntity: []string{"machine-1*", "machine-2"},
   328  		includeModule: []string{"juju", "unit"},
   329  		excludeEntity: []string{"machine-1-lxc*"},
   330  		excludeModule: []string{"juju.provisioner"},
   331  		maxLines:      300,
   332  		backlog:       100,
   333  		filterLevel:   loggo.INFO,
   334  		fromTheStart:  true,
   335  	}
   336  	obtained, err = newLogStream(values)
   337  	c.Assert(err, gc.IsNil)
   338  	assertStreamParams(c, obtained, expected)
   339  
   340  	_, err = newLogStream(url.Values{"maxLines": []string{"foo"}})
   341  	c.Assert(err, gc.ErrorMatches, `maxLines value "foo" is not a valid unsigned number`)
   342  
   343  	_, err = newLogStream(url.Values{"backlog": []string{"foo"}})
   344  	c.Assert(err, gc.ErrorMatches, `backlog value "foo" is not a valid unsigned number`)
   345  
   346  	_, err = newLogStream(url.Values{"replay": []string{"foo"}})
   347  	c.Assert(err, gc.ErrorMatches, `replay value "foo" is not a valid boolean`)
   348  
   349  	_, err = newLogStream(url.Values{"level": []string{"foo"}})
   350  	c.Assert(err, gc.ErrorMatches, `level value "foo" is not one of "TRACE", "DEBUG", "INFO", "WARNING", "ERROR"`)
   351  }