github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/logs_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"math/rand"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/mgo.v2"
    17  	"gopkg.in/mgo.v2/bson"
    18  
    19  	"github.com/juju/juju/state"
    20  	coretesting "github.com/juju/juju/testing"
    21  )
    22  
    23  type LogsSuite struct {
    24  	ConnSuite
    25  	logsColl *mgo.Collection
    26  }
    27  
    28  var _ = gc.Suite(&LogsSuite{})
    29  
    30  func (s *LogsSuite) SetUpTest(c *gc.C) {
    31  	s.ConnSuite.SetUpTest(c)
    32  
    33  	session := s.State.MongoSession()
    34  	s.logsColl = session.DB("logs").C("logs")
    35  }
    36  
    37  func (s *LogsSuite) TestLastSentLoggerSetGet(c *gc.C) {
    38  	logger0 := state.NewLastSentLogger(s.State, "test-sink0")
    39  	logger1 := state.NewLastSentLogger(s.State, "test-sink1")
    40  	t := time.Date(2016, 04, 15, 16, 0, 0, 42, time.UTC)
    41  	err := logger0.Set(t)
    42  	c.Assert(err, jc.ErrorIsNil)
    43  	t1, err := logger0.Get()
    44  	c.Assert(err, jc.ErrorIsNil)
    45  	c.Assert(t1, gc.DeepEquals, t)
    46  	t2 := t.Add(time.Hour)
    47  	err = logger0.Set(t2)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	t3, err := logger0.Get()
    50  	c.Assert(err, jc.ErrorIsNil)
    51  	c.Assert(t3, gc.DeepEquals, t2)
    52  	_, err = logger1.Get()
    53  	c.Assert(err, gc.ErrorMatches, state.ErrNeverForwarded.Error())
    54  
    55  	t5 := time.Date(2016, 4, 15, 16, 0, 0, 43, time.Local)
    56  	err = logger1.Set(t5)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	t6, err := logger1.Get()
    59  	c.Assert(err, jc.ErrorIsNil)
    60  	c.Assert(t6, gc.DeepEquals, t5.UTC())
    61  }
    62  
    63  func (s *LogsSuite) TestLastSentLoggerNoSet(c *gc.C) {
    64  	logger := state.NewLastSentLogger(s.State, "test")
    65  	_, err := logger.Get()
    66  	c.Assert(err, gc.ErrorMatches, state.ErrNeverForwarded.Error())
    67  }
    68  
    69  func (s *LogsSuite) TestIndexesCreated(c *gc.C) {
    70  	// Indexes should be created on the logs collection when state is opened.
    71  	indexes, err := s.logsColl.Indexes()
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	var keys []string
    74  	for _, index := range indexes {
    75  		keys = append(keys, strings.Join(index.Key, "-"))
    76  	}
    77  	c.Assert(keys, jc.SameContents, []string{
    78  		"_id", // default index
    79  		"e-t", // model-uuid and timestamp
    80  		"e-n", // model-uuid and entity
    81  	})
    82  }
    83  
    84  func (s *LogsSuite) TestDbLogger(c *gc.C) {
    85  	logger := state.NewDbLogger(s.State, names.NewMachineTag("22"))
    86  	defer logger.Close()
    87  	t0 := time.Now().Truncate(time.Millisecond) // MongoDB only stores timestamps with ms precision.
    88  	logger.Log(t0, "some.where", "foo.go:99", loggo.INFO, "all is well")
    89  	t1 := t0.Add(time.Second)
    90  	logger.Log(t1, "else.where", "bar.go:42", loggo.ERROR, "oh noes")
    91  
    92  	var docs []bson.M
    93  	err := s.logsColl.Find(nil).Sort("t").All(&docs)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  	c.Assert(docs, gc.HasLen, 2)
    96  
    97  	c.Assert(docs[0]["t"], gc.Equals, t0)
    98  	c.Assert(docs[0]["e"], gc.Equals, s.State.ModelUUID())
    99  	c.Assert(docs[0]["n"], gc.Equals, "machine-22")
   100  	c.Assert(docs[0]["m"], gc.Equals, "some.where")
   101  	c.Assert(docs[0]["l"], gc.Equals, "foo.go:99")
   102  	c.Assert(docs[0]["v"], gc.Equals, int(loggo.INFO))
   103  	c.Assert(docs[0]["x"], gc.Equals, "all is well")
   104  
   105  	c.Assert(docs[1]["t"], gc.Equals, t1)
   106  	c.Assert(docs[1]["e"], gc.Equals, s.State.ModelUUID())
   107  	c.Assert(docs[1]["n"], gc.Equals, "machine-22")
   108  	c.Assert(docs[1]["m"], gc.Equals, "else.where")
   109  	c.Assert(docs[1]["l"], gc.Equals, "bar.go:42")
   110  	c.Assert(docs[1]["v"], gc.Equals, int(loggo.ERROR))
   111  	c.Assert(docs[1]["x"], gc.Equals, "oh noes")
   112  }
   113  
   114  func (s *LogsSuite) TestPruneLogsByTime(c *gc.C) {
   115  	dbLogger := state.NewDbLogger(s.State, names.NewMachineTag("22"))
   116  	defer dbLogger.Close()
   117  	log := func(t time.Time, msg string) {
   118  		err := dbLogger.Log(t, "module", "loc", loggo.INFO, msg)
   119  		c.Assert(err, jc.ErrorIsNil)
   120  	}
   121  
   122  	now := time.Now()
   123  	maxLogTime := now.Add(-time.Minute)
   124  	log(now, "keep")
   125  	log(maxLogTime.Add(time.Second), "keep")
   126  	log(maxLogTime, "keep")
   127  	log(maxLogTime.Add(-time.Second), "prune")
   128  	log(maxLogTime.Add(-(2 * time.Second)), "prune")
   129  
   130  	noPruneMB := 100
   131  	err := state.PruneLogs(s.State, maxLogTime, noPruneMB)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  
   134  	// After pruning there should just be 3 "keep" messages left.
   135  	var docs []bson.M
   136  	err = s.logsColl.Find(nil).All(&docs)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	c.Assert(docs, gc.HasLen, 3)
   139  	for _, doc := range docs {
   140  		c.Assert(doc["x"], gc.Equals, "keep")
   141  	}
   142  }
   143  
   144  func (s *LogsSuite) TestPruneLogsBySize(c *gc.C) {
   145  	// Set up 3 models and generate different amounts of logs
   146  	// for them.
   147  	now := time.Now().Truncate(time.Millisecond)
   148  
   149  	s0 := s.State
   150  	startingLogsS0 := 10
   151  	s.generateLogs(c, s0, now, startingLogsS0)
   152  
   153  	s1 := s.Factory.MakeModel(c, nil)
   154  	defer s1.Close()
   155  	startingLogsS1 := 10000
   156  	s.generateLogs(c, s1, now, startingLogsS1)
   157  
   158  	s2 := s.Factory.MakeModel(c, nil)
   159  	defer s2.Close()
   160  	startingLogsS2 := 12000
   161  	s.generateLogs(c, s2, now, startingLogsS2)
   162  
   163  	// Prune logs collection back to 1 MiB.
   164  	tsNoPrune := time.Now().Add(-3 * 24 * time.Hour)
   165  	err := state.PruneLogs(s.State, tsNoPrune, 1)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  
   168  	// Logs for first env should not be touched.
   169  	c.Assert(s.countLogs(c, s0), gc.Equals, startingLogsS0)
   170  
   171  	// Logs for second env should be pruned.
   172  	c.Assert(s.countLogs(c, s1), jc.LessThan, startingLogsS1)
   173  
   174  	// Logs for third env should be pruned to a similar level as
   175  	// second env.
   176  	c.Assert(s.countLogs(c, s2), jc.LessThan, startingLogsS1)
   177  
   178  	// Ensure that the latest log records are still there.
   179  	assertLatestTs := func(st *state.State) {
   180  		var doc bson.M
   181  		err := s.logsColl.Find(bson.M{"e": st.ModelUUID()}).Sort("-t").One(&doc)
   182  		c.Assert(err, jc.ErrorIsNil)
   183  		c.Assert(doc["t"].(time.Time), gc.Equals, now)
   184  	}
   185  	assertLatestTs(s0)
   186  	assertLatestTs(s1)
   187  	assertLatestTs(s2)
   188  }
   189  
   190  func (s *LogsSuite) generateLogs(c *gc.C, st *state.State, endTime time.Time, count int) {
   191  	dbLogger := state.NewDbLogger(st, names.NewMachineTag("0"))
   192  	defer dbLogger.Close()
   193  	for i := 0; i < count; i++ {
   194  		ts := endTime.Add(-time.Duration(i) * time.Second)
   195  		err := dbLogger.Log(ts, "module", "loc", loggo.INFO, "message")
   196  		c.Assert(err, jc.ErrorIsNil)
   197  	}
   198  }
   199  
   200  func (s *LogsSuite) countLogs(c *gc.C, st *state.State) int {
   201  	count, err := s.logsColl.Find(bson.M{"e": st.ModelUUID()}).Count()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	return count
   204  }
   205  
   206  type LogTailerSuite struct {
   207  	ConnSuite
   208  	logsColl   *mgo.Collection
   209  	oplogColl  *mgo.Collection
   210  	otherState *state.State
   211  }
   212  
   213  var _ = gc.Suite(&LogTailerSuite{})
   214  
   215  func (s *LogTailerSuite) SetUpTest(c *gc.C) {
   216  	s.ConnSuite.SetUpTest(c)
   217  
   218  	session := s.State.MongoSession()
   219  	s.logsColl = session.DB("logs").C("logs")
   220  
   221  	// Create a fake oplog collection.
   222  	s.oplogColl = session.DB("logs").C("oplog.fake")
   223  	err := s.oplogColl.Create(&mgo.CollectionInfo{
   224  		Capped:   true,
   225  		MaxBytes: 1024 * 1024,
   226  	})
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	s.AddCleanup(func(*gc.C) { s.oplogColl.DropCollection() })
   229  
   230  	s.otherState = s.NewStateForModelNamed(c, "test-model")
   231  	c.Assert(s.otherState, gc.NotNil)
   232  	s.AddCleanup(func(c *gc.C) {
   233  		err := s.otherState.Close()
   234  		c.Assert(err, jc.ErrorIsNil)
   235  	})
   236  }
   237  
   238  func (s *LogTailerSuite) TestTimeFiltering(c *gc.C) {
   239  	// Add 10 logs that shouldn't be returned.
   240  	threshT := time.Now()
   241  	s.writeLogsT(c,
   242  		threshT.Add(-5*time.Second), threshT.Add(-time.Millisecond), 5,
   243  		logTemplate{Message: "dont want"},
   244  	)
   245  
   246  	// Add 5 logs that should be returned.
   247  	want := logTemplate{Message: "want"}
   248  	s.writeLogsT(c, threshT, threshT.Add(5*time.Second), 5, want)
   249  	tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{
   250  		StartTime: threshT,
   251  		Oplog:     s.oplogColl,
   252  	})
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	defer tailer.Stop()
   255  	s.assertTailer(c, tailer, 5, want)
   256  
   257  	// Write more logs. These will be read from the the oplog.
   258  	want2 := logTemplate{Message: "want 2"}
   259  	s.writeLogsT(c, threshT.Add(6*time.Second), threshT.Add(10*time.Second), 5, want2)
   260  	s.assertTailer(c, tailer, 5, want2)
   261  
   262  }
   263  
   264  func (s *LogTailerSuite) TestOplogTransition(c *gc.C) {
   265  	// Ensure that logs aren't repeated as the log tailer moves from
   266  	// reading from the logs collection to tailing the oplog.
   267  	//
   268  	// All logs are written out with the same timestamp to create a
   269  	// challenging scenario for the tailer.
   270  
   271  	for i := 0; i < 5; i++ {
   272  		s.writeLogs(c, 1, logTemplate{Message: strconv.Itoa(i)})
   273  	}
   274  
   275  	tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{
   276  		Oplog: s.oplogColl,
   277  	})
   278  	c.Assert(err, jc.ErrorIsNil)
   279  	defer tailer.Stop()
   280  	for i := 0; i < 5; i++ {
   281  		s.assertTailer(c, tailer, 1, logTemplate{Message: strconv.Itoa(i)})
   282  	}
   283  
   284  	// Write more logs. These will be read from the the oplog.
   285  	for i := 5; i < 10; i++ {
   286  		lt := logTemplate{Message: strconv.Itoa(i)}
   287  		s.writeLogs(c, 2, lt)
   288  		s.assertTailer(c, tailer, 2, lt)
   289  	}
   290  }
   291  
   292  func (s *LogTailerSuite) TestModelFiltering(c *gc.C) {
   293  	good := logTemplate{Message: "good"}
   294  	writeLogs := func() {
   295  		s.writeLogs(c, 1, logTemplate{
   296  			ModelUUID: "someuuid0",
   297  			Message:   "bad",
   298  		})
   299  		s.writeLogs(c, 1, logTemplate{
   300  			ModelUUID: "someuuid1",
   301  			Message:   "bad",
   302  		})
   303  		s.writeLogs(c, 1, good)
   304  	}
   305  
   306  	assert := func(tailer state.LogTailer) {
   307  		// Only the entries the s.State's UUID should be reported.
   308  		s.assertTailer(c, tailer, 1, good)
   309  	}
   310  
   311  	s.checkLogTailerFiltering(c, s.otherState, &state.LogTailerParams{}, writeLogs, assert)
   312  }
   313  
   314  func (s *LogTailerSuite) TestTailingLogsForAllModels(c *gc.C) {
   315  	writeLogs := func() {
   316  		s.writeLogs(c, 1, logTemplate{
   317  			ModelUUID: "someuuid0",
   318  			Message:   "bad",
   319  		})
   320  		s.writeLogs(c, 1, logTemplate{
   321  			ModelUUID: "someuuid1",
   322  			Message:   "bad",
   323  		})
   324  		s.writeLogs(c, 1, logTemplate{Message: "good"})
   325  	}
   326  
   327  	assert := func(tailer state.LogTailer) {
   328  		messagesFrom := map[string]bool{
   329  			s.otherState.ModelUUID(): false,
   330  			"someuuid0":              false,
   331  			"someuuid1":              false,
   332  		}
   333  		defer func() {
   334  			c.Assert(messagesFrom, gc.HasLen, 3)
   335  			for _, v := range messagesFrom {
   336  				c.Assert(v, jc.IsTrue)
   337  			}
   338  		}()
   339  		count := 0
   340  		for {
   341  			select {
   342  			case log := <-tailer.Logs():
   343  				messagesFrom[log.ModelUUID] = true
   344  				count++
   345  				c.Logf("count %d", count)
   346  				if count >= 3 {
   347  					return
   348  				}
   349  			case <-time.After(coretesting.ShortWait):
   350  				c.Fatalf("timeout waiting for logs %d", count)
   351  			}
   352  		}
   353  	}
   354  	s.checkLogTailerFiltering(c, s.State, &state.LogTailerParams{AllModels: true}, writeLogs, assert)
   355  }
   356  
   357  func (s *LogTailerSuite) TestTailingLogsOnlyForControllerModel(c *gc.C) {
   358  	writeLogs := func() {
   359  		s.writeLogs(c, 1, logTemplate{
   360  			ModelUUID: s.otherState.ModelUUID(),
   361  			Message:   "bad"},
   362  		)
   363  		s.writeLogs(c, 1, logTemplate{
   364  			ModelUUID: s.State.ModelUUID(),
   365  			Message:   "good1",
   366  		})
   367  		s.writeLogs(c, 1, logTemplate{
   368  			ModelUUID: s.State.ModelUUID(),
   369  			Message:   "good2",
   370  		})
   371  	}
   372  
   373  	assert := func(tailer state.LogTailer) {
   374  		messages := map[string]bool{}
   375  		defer func() {
   376  			c.Assert(messages, gc.HasLen, 2)
   377  			for m, _ := range messages {
   378  				if m != "good1" && m != "good2" {
   379  					c.Fatalf("received message: %v", m)
   380  				}
   381  			}
   382  		}()
   383  		count := 0
   384  		for {
   385  			select {
   386  			case log := <-tailer.Logs():
   387  				c.Assert(log.ModelUUID, gc.Equals, s.State.ModelUUID())
   388  				messages[log.Message] = true
   389  				count++
   390  				c.Logf("count %d", count)
   391  				if count >= 2 {
   392  					return
   393  				}
   394  			case <-time.After(coretesting.ShortWait):
   395  				c.Fatalf("timeout waiting for logs %d", count)
   396  			}
   397  		}
   398  	}
   399  	s.checkLogTailerFiltering(c, s.State, &state.LogTailerParams{}, writeLogs, assert)
   400  }
   401  
   402  func (s *LogTailerSuite) TestTailingAllLogsFromNonController(c *gc.C) {
   403  	_, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{AllModels: true})
   404  	c.Assert(err, gc.ErrorMatches, "not allowed to tail logs from all models: not a controller")
   405  }
   406  
   407  func (s *LogTailerSuite) TestLevelFiltering(c *gc.C) {
   408  	info := logTemplate{Level: loggo.INFO}
   409  	error := logTemplate{Level: loggo.ERROR}
   410  	writeLogs := func() {
   411  		s.writeLogs(c, 1, logTemplate{Level: loggo.DEBUG})
   412  		s.writeLogs(c, 1, info)
   413  		s.writeLogs(c, 1, error)
   414  	}
   415  	params := &state.LogTailerParams{
   416  		MinLevel: loggo.INFO,
   417  	}
   418  	assert := func(tailer state.LogTailer) {
   419  		s.assertTailer(c, tailer, 1, info)
   420  		s.assertTailer(c, tailer, 1, error)
   421  	}
   422  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   423  }
   424  
   425  func (s *LogTailerSuite) TestInitialLines(c *gc.C) {
   426  	expected := logTemplate{Message: "want"}
   427  	s.writeLogs(c, 3, logTemplate{Message: "dont want"})
   428  	s.writeLogs(c, 5, expected)
   429  
   430  	tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{
   431  		InitialLines: 5,
   432  	})
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	defer tailer.Stop()
   435  
   436  	// Should see just the last 5 lines as requested.
   437  	s.assertTailer(c, tailer, 5, expected)
   438  }
   439  
   440  func (s *LogTailerSuite) TestInitialLinesWithNotEnoughLines(c *gc.C) {
   441  	expected := logTemplate{Message: "want"}
   442  	s.writeLogs(c, 2, expected)
   443  
   444  	tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{
   445  		InitialLines: 5,
   446  	})
   447  	c.Assert(err, jc.ErrorIsNil)
   448  	defer tailer.Stop()
   449  
   450  	// Should see just the 2 lines that existed, even though 5 were
   451  	// asked for.
   452  	s.assertTailer(c, tailer, 2, expected)
   453  }
   454  
   455  func (s *LogTailerSuite) TestNoTail(c *gc.C) {
   456  	expected := logTemplate{Message: "want"}
   457  	s.writeLogs(c, 2, expected)
   458  
   459  	// Write a log entry that's only in the oplog.
   460  	doc := s.logTemplateToDoc(logTemplate{Message: "dont want"}, time.Now())
   461  	err := s.writeLogToOplog(doc)
   462  	c.Assert(err, jc.ErrorIsNil)
   463  
   464  	tailer, err := state.NewLogTailer(s.otherState, &state.LogTailerParams{
   465  		NoTail: true,
   466  	})
   467  	c.Assert(err, jc.ErrorIsNil)
   468  	// Not strictly necessary, just in case NoTail doesn't work in the test.
   469  	defer tailer.Stop()
   470  
   471  	// Logs only in the oplog shouldn't be reported and the tailer
   472  	// should stop itself once the log collection has been read.
   473  	s.assertTailer(c, tailer, 2, expected)
   474  	select {
   475  	case _, ok := <-tailer.Logs():
   476  		if ok {
   477  			c.Fatal("shouldn't be any further logs")
   478  		}
   479  	case <-time.After(coretesting.LongWait):
   480  		c.Fatal("timed out waiting for logs channel to close")
   481  	}
   482  
   483  	select {
   484  	case <-tailer.Dying():
   485  		// Success.
   486  	case <-time.After(coretesting.LongWait):
   487  		c.Fatal("tailer didn't stop itself")
   488  	}
   489  }
   490  
   491  func (s *LogTailerSuite) TestIncludeEntity(c *gc.C) {
   492  	machine0 := logTemplate{Entity: names.NewMachineTag("0")}
   493  	foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")}
   494  	foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")}
   495  	writeLogs := func() {
   496  		s.writeLogs(c, 3, machine0)
   497  		s.writeLogs(c, 2, foo0)
   498  		s.writeLogs(c, 1, foo1)
   499  		s.writeLogs(c, 3, machine0)
   500  	}
   501  	params := &state.LogTailerParams{
   502  		IncludeEntity: []string{
   503  			"unit-foo-0",
   504  			"unit-foo-1",
   505  		},
   506  	}
   507  	assert := func(tailer state.LogTailer) {
   508  		s.assertTailer(c, tailer, 2, foo0)
   509  		s.assertTailer(c, tailer, 1, foo1)
   510  	}
   511  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   512  }
   513  
   514  func (s *LogTailerSuite) TestIncludeEntityWildcard(c *gc.C) {
   515  	machine0 := logTemplate{Entity: names.NewMachineTag("0")}
   516  	foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")}
   517  	foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")}
   518  	writeLogs := func() {
   519  		s.writeLogs(c, 3, machine0)
   520  		s.writeLogs(c, 2, foo0)
   521  		s.writeLogs(c, 1, foo1)
   522  		s.writeLogs(c, 3, machine0)
   523  	}
   524  	params := &state.LogTailerParams{
   525  		IncludeEntity: []string{
   526  			"unit-foo*",
   527  		},
   528  	}
   529  	assert := func(tailer state.LogTailer) {
   530  		s.assertTailer(c, tailer, 2, foo0)
   531  		s.assertTailer(c, tailer, 1, foo1)
   532  	}
   533  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   534  }
   535  
   536  func (s *LogTailerSuite) TestExcludeEntity(c *gc.C) {
   537  	machine0 := logTemplate{Entity: names.NewMachineTag("0")}
   538  	foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")}
   539  	foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")}
   540  	writeLogs := func() {
   541  		s.writeLogs(c, 3, machine0)
   542  		s.writeLogs(c, 2, foo0)
   543  		s.writeLogs(c, 1, foo1)
   544  		s.writeLogs(c, 3, machine0)
   545  	}
   546  	params := &state.LogTailerParams{
   547  		ExcludeEntity: []string{
   548  			"machine-0",
   549  			"unit-foo-0",
   550  		},
   551  	}
   552  	assert := func(tailer state.LogTailer) {
   553  		s.assertTailer(c, tailer, 1, foo1)
   554  	}
   555  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   556  }
   557  
   558  func (s *LogTailerSuite) TestExcludeEntityWildcard(c *gc.C) {
   559  	machine0 := logTemplate{Entity: names.NewMachineTag("0")}
   560  	foo0 := logTemplate{Entity: names.NewUnitTag("foo/0")}
   561  	foo1 := logTemplate{Entity: names.NewUnitTag("foo/1")}
   562  	writeLogs := func() {
   563  		s.writeLogs(c, 3, machine0)
   564  		s.writeLogs(c, 2, foo0)
   565  		s.writeLogs(c, 1, foo1)
   566  		s.writeLogs(c, 3, machine0)
   567  	}
   568  	params := &state.LogTailerParams{
   569  		ExcludeEntity: []string{
   570  			"machine*",
   571  			"unit-*-0",
   572  		},
   573  	}
   574  	assert := func(tailer state.LogTailer) {
   575  		s.assertTailer(c, tailer, 1, foo1)
   576  	}
   577  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   578  }
   579  func (s *LogTailerSuite) TestIncludeModule(c *gc.C) {
   580  	mod0 := logTemplate{Module: "foo.bar"}
   581  	mod1 := logTemplate{Module: "juju.thing"}
   582  	subMod1 := logTemplate{Module: "juju.thing.hai"}
   583  	mod2 := logTemplate{Module: "elsewhere"}
   584  	writeLogs := func() {
   585  		s.writeLogs(c, 1, mod0)
   586  		s.writeLogs(c, 1, mod1)
   587  		s.writeLogs(c, 1, mod0)
   588  		s.writeLogs(c, 1, subMod1)
   589  		s.writeLogs(c, 1, mod0)
   590  		s.writeLogs(c, 1, mod2)
   591  	}
   592  	params := &state.LogTailerParams{
   593  		IncludeModule: []string{"juju.thing", "elsewhere"},
   594  	}
   595  	assert := func(tailer state.LogTailer) {
   596  		s.assertTailer(c, tailer, 1, mod1)
   597  		s.assertTailer(c, tailer, 1, subMod1)
   598  		s.assertTailer(c, tailer, 1, mod2)
   599  	}
   600  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   601  }
   602  
   603  func (s *LogTailerSuite) TestExcludeModule(c *gc.C) {
   604  	mod0 := logTemplate{Module: "foo.bar"}
   605  	mod1 := logTemplate{Module: "juju.thing"}
   606  	subMod1 := logTemplate{Module: "juju.thing.hai"}
   607  	mod2 := logTemplate{Module: "elsewhere"}
   608  	writeLogs := func() {
   609  		s.writeLogs(c, 1, mod0)
   610  		s.writeLogs(c, 1, mod1)
   611  		s.writeLogs(c, 1, mod0)
   612  		s.writeLogs(c, 1, subMod1)
   613  		s.writeLogs(c, 1, mod0)
   614  		s.writeLogs(c, 1, mod2)
   615  	}
   616  	params := &state.LogTailerParams{
   617  		ExcludeModule: []string{"juju.thing", "elsewhere"},
   618  	}
   619  	assert := func(tailer state.LogTailer) {
   620  		s.assertTailer(c, tailer, 2, mod0)
   621  	}
   622  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   623  }
   624  
   625  func (s *LogTailerSuite) TestIncludeExcludeModule(c *gc.C) {
   626  	foo := logTemplate{Module: "foo"}
   627  	bar := logTemplate{Module: "bar"}
   628  	barSub := logTemplate{Module: "bar.thing"}
   629  	baz := logTemplate{Module: "baz"}
   630  	qux := logTemplate{Module: "qux"}
   631  	writeLogs := func() {
   632  		s.writeLogs(c, 1, foo)
   633  		s.writeLogs(c, 1, bar)
   634  		s.writeLogs(c, 1, barSub)
   635  		s.writeLogs(c, 1, baz)
   636  		s.writeLogs(c, 1, qux)
   637  	}
   638  	params := &state.LogTailerParams{
   639  		IncludeModule: []string{"foo", "bar", "qux"},
   640  		ExcludeModule: []string{"foo", "bar"},
   641  	}
   642  	assert := func(tailer state.LogTailer) {
   643  		// Except just "qux" because "foo" and "bar" were included and
   644  		// then excluded.
   645  		s.assertTailer(c, tailer, 1, qux)
   646  	}
   647  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   648  }
   649  
   650  func (s *LogTailerSuite) checkLogTailerFiltering(
   651  	c *gc.C,
   652  	st *state.State,
   653  	params *state.LogTailerParams,
   654  	writeLogs func(),
   655  	assertTailer func(state.LogTailer),
   656  ) {
   657  	// Check the tailer does the right thing when reading from the
   658  	// logs collection.
   659  	writeLogs()
   660  	params.Oplog = s.oplogColl
   661  	tailer, err := state.NewLogTailer(st, params)
   662  	c.Assert(err, jc.ErrorIsNil)
   663  	defer tailer.Stop()
   664  	assertTailer(tailer)
   665  
   666  	// Now write out logs and check the tailer again. These will be
   667  	// read from the oplog.
   668  	writeLogs()
   669  	assertTailer(tailer)
   670  }
   671  
   672  type logTemplate struct {
   673  	ModelUUID string
   674  	Entity    names.Tag
   675  	Module    string
   676  	Location  string
   677  	Level     loggo.Level
   678  	Message   string
   679  }
   680  
   681  // writeLogs creates count log messages at the current time using
   682  // the supplied template. As well as writing to the logs collection,
   683  // entries are also made into the fake oplog collection.
   684  func (s *LogTailerSuite) writeLogs(c *gc.C, count int, lt logTemplate) {
   685  	t := time.Now()
   686  	s.writeLogsT(c, t, t, count, lt)
   687  }
   688  
   689  // writeLogsT creates count log messages between startTime and
   690  // endTime using the supplied template. As well as writing to the logs
   691  // collection, entries are also made into the fake oplog collection.
   692  func (s *LogTailerSuite) writeLogsT(c *gc.C, startTime, endTime time.Time, count int, lt logTemplate) {
   693  	interval := endTime.Sub(startTime) / time.Duration(count)
   694  	t := startTime
   695  	for i := 0; i < count; i++ {
   696  		doc := s.logTemplateToDoc(lt, t)
   697  		err := s.writeLogToOplog(doc)
   698  		c.Assert(err, jc.ErrorIsNil)
   699  		err = s.logsColl.Insert(doc)
   700  		c.Assert(err, jc.ErrorIsNil)
   701  		t = t.Add(interval)
   702  	}
   703  }
   704  
   705  // writeLogToOplog writes out a log record to the a (probably fake)
   706  // oplog collection.
   707  func (s *LogTailerSuite) writeLogToOplog(doc interface{}) error {
   708  	return s.oplogColl.Insert(bson.D{
   709  		{"ts", bson.MongoTimestamp(time.Now().Unix() << 32)}, // an approximation which will do
   710  		{"h", rand.Int63()},                                  // again, a suitable fake
   711  		{"op", "i"},                                          // this will always be an insert
   712  		{"ns", "logs.logs"},
   713  		{"o", doc},
   714  	})
   715  }
   716  
   717  func (s *LogTailerSuite) normaliseLogTemplate(lt *logTemplate) {
   718  	if lt.ModelUUID == "" {
   719  		lt.ModelUUID = s.otherState.ModelUUID()
   720  	}
   721  	if lt.Entity == nil {
   722  		lt.Entity = names.NewMachineTag("0")
   723  	}
   724  	if lt.Module == "" {
   725  		lt.Module = "module"
   726  	}
   727  	if lt.Location == "" {
   728  		lt.Location = "loc"
   729  	}
   730  	if lt.Level == loggo.UNSPECIFIED {
   731  		lt.Level = loggo.INFO
   732  	}
   733  	if lt.Message == "" {
   734  		lt.Message = "message"
   735  	}
   736  }
   737  
   738  func (s *LogTailerSuite) logTemplateToDoc(lt logTemplate, t time.Time) interface{} {
   739  	s.normaliseLogTemplate(&lt)
   740  	return state.MakeLogDoc(
   741  		lt.ModelUUID,
   742  		lt.Entity,
   743  		t,
   744  		lt.Module,
   745  		lt.Location,
   746  		lt.Level,
   747  		lt.Message,
   748  	)
   749  }
   750  
   751  func (s *LogTailerSuite) assertTailer(c *gc.C, tailer state.LogTailer, expectedCount int, lt logTemplate) {
   752  	s.normaliseLogTemplate(&lt)
   753  
   754  	timeout := time.After(coretesting.LongWait)
   755  	count := 0
   756  	for {
   757  		select {
   758  		case log, ok := <-tailer.Logs():
   759  			if !ok {
   760  				c.Fatalf("tailer died unexpectedly: %v", tailer.Err())
   761  			}
   762  			c.Assert(log.Entity, gc.Equals, lt.Entity.String())
   763  			c.Assert(log.Module, gc.Equals, lt.Module)
   764  			c.Assert(log.Location, gc.Equals, lt.Location)
   765  			c.Assert(log.Level, gc.Equals, lt.Level)
   766  			c.Assert(log.Message, gc.Equals, lt.Message)
   767  			c.Assert(log.ModelUUID, gc.Equals, lt.ModelUUID)
   768  			count++
   769  			if count == expectedCount {
   770  				return
   771  			}
   772  		case <-timeout:
   773  			c.Fatalf("timed out waiting for logs (received %d)", count)
   774  		}
   775  	}
   776  }