github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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/mgo/v3"
    14  	"github.com/juju/mgo/v3/bson"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/version/v2"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	corelogger "github.com/juju/juju/core/logger"
    20  	"github.com/juju/juju/state"
    21  	coretesting "github.com/juju/juju/testing"
    22  	jujuversion "github.com/juju/juju/version"
    23  )
    24  
    25  type LogCollectionSuite struct {
    26  	ConnSuite
    27  }
    28  
    29  var _ = gc.Suite(&LogCollectionSuite{})
    30  
    31  func (s *LogCollectionSuite) TestCreateCollection(c *gc.C) {
    32  	session := s.State.MongoSession()
    33  	modelUUID := "00000000-0000-0000-0000-000000000001"
    34  
    35  	coll := session.DB("logs").C("logs." + modelUUID)
    36  
    37  	// Loop to test idempotency.
    38  	for i := 0; i < 2; i++ {
    39  		err := state.InitDbLogsForModel(s.Session, modelUUID, 1)
    40  		c.Assert(err, jc.ErrorIsNil)
    41  		capped, size, err := state.GetCollectionCappedInfo(coll)
    42  		c.Assert(err, jc.ErrorIsNil)
    43  		c.Assert(capped, jc.IsTrue)
    44  		c.Assert(size, gc.Equals, 1)
    45  	}
    46  }
    47  
    48  func (s *LogCollectionSuite) TestUpgradeCollection(c *gc.C) {
    49  	session := s.State.MongoSession()
    50  	modelUUID := "00000000-0000-0000-0000-000000000002"
    51  
    52  	coll := session.DB("logs").C("logs." + modelUUID)
    53  	// Create a non-capped collection.
    54  	err := coll.Create(&mgo.CollectionInfo{})
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	capped, size, err := state.GetCollectionCappedInfo(coll)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	c.Assert(capped, jc.IsFalse)
    59  	c.Assert(size, gc.Equals, 0)
    60  
    61  	// Ensure collection is "upgraded" to a capped collection.
    62  	err = state.InitDbLogsForModel(s.Session, modelUUID, 1)
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	capped, size, err = state.GetCollectionCappedInfo(coll)
    65  	c.Assert(err, jc.ErrorIsNil)
    66  	c.Assert(capped, jc.IsTrue)
    67  	c.Assert(size, gc.Equals, 1)
    68  }
    69  
    70  func (s *LogCollectionSuite) TestResizeCollection(c *gc.C) {
    71  	session := s.State.MongoSession()
    72  	modelUUID := "00000000-0000-0000-0000-000000000003"
    73  
    74  	coll := session.DB("logs").C("logs." + modelUUID)
    75  	// Create a small collection.
    76  	err := state.InitDbLogsForModel(s.Session, modelUUID, 2)
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	capped, size, err := state.GetCollectionCappedInfo(coll)
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	c.Assert(capped, jc.IsTrue)
    81  	c.Assert(size, gc.Equals, 2)
    82  
    83  	// Make it bigger.
    84  	err = state.InitDbLogsForModel(s.Session, modelUUID, 3)
    85  	c.Assert(err, jc.ErrorIsNil)
    86  	capped, size, err = state.GetCollectionCappedInfo(coll)
    87  	c.Assert(err, jc.ErrorIsNil)
    88  	c.Assert(capped, jc.IsTrue)
    89  	c.Assert(size, gc.Equals, 3)
    90  
    91  	// Make it even smaller.
    92  	err = state.InitDbLogsForModel(s.Session, modelUUID, 1)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	capped, size, err = state.GetCollectionCappedInfo(coll)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	c.Assert(capped, jc.IsTrue)
    97  	c.Assert(size, gc.Equals, 1)
    98  }
    99  
   100  type LogsSuite struct {
   101  	ConnSuite
   102  	logsColl *mgo.Collection
   103  
   104  	logger loggo.Logger
   105  }
   106  
   107  var _ = gc.Suite(&LogsSuite{})
   108  
   109  func (s *LogsSuite) SetUpTest(c *gc.C) {
   110  	s.ConnSuite.SetUpTest(c)
   111  	s.logsColl = s.logCollFor(s.State)
   112  	s.logger = loggo.GetLogger("test")
   113  }
   114  
   115  func (s *LogsSuite) logCollFor(st *state.State) *mgo.Collection {
   116  	session := st.MongoSession()
   117  	return session.DB("logs").C("logs." + st.ModelUUID())
   118  }
   119  
   120  func (s *LogsSuite) TestLastSentLogTrackerSetGet(c *gc.C) {
   121  	tracker := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink")
   122  	defer tracker.Close()
   123  
   124  	err := tracker.Set(10, 100)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	id1, ts1, err := tracker.Get()
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	err = tracker.Set(20, 200)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	id2, ts2, err := tracker.Get()
   131  	c.Assert(err, jc.ErrorIsNil)
   132  
   133  	c.Check(id1, gc.Equals, int64(10))
   134  	c.Check(ts1, gc.Equals, int64(100))
   135  	c.Check(id2, gc.Equals, int64(20))
   136  	c.Check(ts2, gc.Equals, int64(200))
   137  }
   138  
   139  func (s *LogsSuite) TestLastSentLogTrackerGetNeverSet(c *gc.C) {
   140  	tracker := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test")
   141  	defer tracker.Close()
   142  
   143  	_, _, err := tracker.Get()
   144  
   145  	c.Check(err, gc.ErrorMatches, state.ErrNeverForwarded.Error())
   146  }
   147  
   148  func (s *LogsSuite) TestLastSentLogTrackerIndependentModels(c *gc.C) {
   149  	tracker0 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink")
   150  	defer tracker0.Close()
   151  	otherModel := s.NewStateForModelNamed(c, "test-model")
   152  	defer otherModel.Close()
   153  	tracker1 := state.NewLastSentLogTracker(otherModel, otherModel.ModelUUID(), "test-sink") // same sink
   154  	defer tracker1.Close()
   155  	err := tracker0.Set(10, 100)
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	id0, ts0, err := tracker0.Get()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	c.Assert(id0, gc.Equals, int64(10))
   160  	c.Assert(ts0, gc.Equals, int64(100))
   161  
   162  	_, _, errBefore := tracker1.Get()
   163  	err = tracker1.Set(20, 200)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	id1, ts1, errAfter := tracker1.Get()
   166  	c.Assert(errAfter, jc.ErrorIsNil)
   167  	id0, ts0, err = tracker0.Get()
   168  	c.Assert(err, jc.ErrorIsNil)
   169  
   170  	c.Check(errBefore, gc.ErrorMatches, state.ErrNeverForwarded.Error())
   171  	c.Check(id1, gc.Equals, int64(20))
   172  	c.Check(ts1, gc.Equals, int64(200))
   173  	c.Check(id0, gc.Equals, int64(10))
   174  	c.Check(ts0, gc.Equals, int64(100))
   175  }
   176  
   177  func (s *LogsSuite) TestLastSentLogTrackerIndependentSinks(c *gc.C) {
   178  	tracker0 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink0")
   179  	defer tracker0.Close()
   180  	tracker1 := state.NewLastSentLogTracker(s.State, s.State.ModelUUID(), "test-sink1")
   181  	defer tracker1.Close()
   182  	err := tracker0.Set(10, 100)
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	id0, ts0, err := tracker0.Get()
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(id0, gc.Equals, int64(10))
   187  	c.Assert(ts0, gc.Equals, int64(100))
   188  
   189  	_, _, errBefore := tracker1.Get()
   190  	err = tracker1.Set(20, 200)
   191  	c.Assert(err, jc.ErrorIsNil)
   192  	id1, ts1, errAfter := tracker1.Get()
   193  	c.Assert(errAfter, jc.ErrorIsNil)
   194  	id0, ts0, err = tracker0.Get()
   195  	c.Assert(err, jc.ErrorIsNil)
   196  
   197  	c.Check(errBefore, gc.ErrorMatches, state.ErrNeverForwarded.Error())
   198  	c.Check(id1, gc.Equals, int64(20))
   199  	c.Check(ts1, gc.Equals, int64(200))
   200  	c.Check(id0, gc.Equals, int64(10))
   201  	c.Check(ts0, gc.Equals, int64(100))
   202  }
   203  
   204  func (s *LogsSuite) TestIndexesCreated(c *gc.C) {
   205  	// Indexes should be created on the logs collection when state is opened.
   206  	indexes, err := s.logsColl.Indexes()
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	var keys []string
   209  	for _, index := range indexes {
   210  		keys = append(keys, strings.Join(index.Key, "-"))
   211  	}
   212  	c.Assert(keys, jc.SameContents, []string{
   213  		"_id",   // default index
   214  		"t-_id", // timestamp and ID
   215  		"n",     // entity
   216  		"c",     // optional labels
   217  	})
   218  }
   219  
   220  func (s *LogsSuite) TestDbLogger(c *gc.C) {
   221  	logger := state.NewDbLogger(s.State)
   222  	defer logger.Close()
   223  
   224  	t0 := coretesting.ZeroTime().Truncate(time.Millisecond) // MongoDB only stores timestamps with ms precision.
   225  	t1 := t0.Add(time.Second)
   226  	err := logger.Log([]corelogger.LogRecord{{
   227  		Time:     t0,
   228  		Entity:   "machine-45",
   229  		Module:   "some.where",
   230  		Location: "foo.go:99",
   231  		Level:    loggo.INFO,
   232  		Message:  "all is well",
   233  	}, {
   234  		Time:     t1,
   235  		Entity:   "machine-47",
   236  		Module:   "else.where",
   237  		Location: "bar.go:42",
   238  		Level:    loggo.ERROR,
   239  		Message:  "oh noes",
   240  	}})
   241  	c.Assert(err, jc.ErrorIsNil)
   242  
   243  	var docs []bson.M
   244  	err = s.logsColl.Find(nil).Sort("t").All(&docs)
   245  	c.Assert(err, jc.ErrorIsNil)
   246  	c.Assert(docs, gc.HasLen, 2)
   247  
   248  	c.Assert(docs[0]["t"], gc.Equals, t0.UnixNano())
   249  	c.Assert(docs[0]["n"], gc.Equals, "machine-45")
   250  	c.Assert(docs[0]["m"], gc.Equals, "some.where")
   251  	c.Assert(docs[0]["l"], gc.Equals, "foo.go:99")
   252  	c.Assert(docs[0]["v"], gc.Equals, int(loggo.INFO))
   253  	c.Assert(docs[0]["x"], gc.Equals, "all is well")
   254  
   255  	c.Assert(docs[1]["t"], gc.Equals, t1.UnixNano())
   256  	c.Assert(docs[1]["n"], gc.Equals, "machine-47")
   257  	c.Assert(docs[1]["m"], gc.Equals, "else.where")
   258  	c.Assert(docs[1]["l"], gc.Equals, "bar.go:42")
   259  	c.Assert(docs[1]["v"], gc.Equals, int(loggo.ERROR))
   260  	c.Assert(docs[1]["x"], gc.Equals, "oh noes")
   261  }
   262  
   263  type LogTailerSuite struct {
   264  	ConnWithWallClockSuite
   265  	oplogColl            *mgo.Collection
   266  	otherState           *state.State
   267  	modelUUID, otherUUID string
   268  }
   269  
   270  var _ = gc.Suite(&LogTailerSuite{})
   271  
   272  func (s *LogTailerSuite) SetUpTest(c *gc.C) {
   273  	s.ConnWithWallClockSuite.SetUpTest(c)
   274  
   275  	session := s.State.MongoSession()
   276  	// Create a fake oplog collection.
   277  	s.oplogColl = session.DB("logs").C("oplog.fake")
   278  	err := s.oplogColl.Create(&mgo.CollectionInfo{
   279  		Capped:   true,
   280  		MaxBytes: 1024 * 1024,
   281  	})
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	s.AddCleanup(func(*gc.C) { s.oplogColl.DropCollection() })
   284  
   285  	s.otherState = s.NewStateForModelNamed(c, "test-model")
   286  	c.Assert(s.otherState, gc.NotNil)
   287  	s.AddCleanup(func(c *gc.C) {
   288  		err := s.otherState.Close()
   289  		c.Assert(err, jc.ErrorIsNil)
   290  	})
   291  	s.modelUUID = s.State.ModelUUID()
   292  	s.otherUUID = s.otherState.ModelUUID()
   293  }
   294  
   295  func (s *LogTailerSuite) getCollection(modelUUID string) *mgo.Collection {
   296  	return s.State.MongoSession().DB("logs").C("logs." + modelUUID)
   297  }
   298  
   299  func (s *LogTailerSuite) TestLogDeletionDuringTailing(c *gc.C) {
   300  	var tw loggo.TestWriter
   301  	err := loggo.RegisterWriter("test", &tw)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	defer loggo.RemoveWriter("test")
   304  
   305  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, s.oplogColl)
   306  	c.Assert(err, jc.ErrorIsNil)
   307  	defer tailer.Stop()
   308  
   309  	want := logTemplate{Message: "want"}
   310  
   311  	s.writeLogs(c, s.otherUUID, 2, want)
   312  	// Delete something.
   313  	s.deleteLogOplogEntry(s.otherUUID, want)
   314  	s.writeLogs(c, s.otherUUID, 2, want)
   315  
   316  	s.assertTailer(c, tailer, 4, want)
   317  
   318  	c.Assert(tw.Log(), gc.Not(jc.LogMatches), jc.SimpleMessages{{
   319  		loggo.WARNING,
   320  		`.*log deserialization failed.*`,
   321  	}})
   322  }
   323  
   324  func (s *LogTailerSuite) TestTimeFiltering(c *gc.C) {
   325  	// Add 10 logs that shouldn't be returned.
   326  	threshT := coretesting.NonZeroTime()
   327  	s.writeLogsT(c,
   328  		s.otherUUID,
   329  		threshT.Add(-5*time.Second), threshT.Add(-time.Millisecond), 5,
   330  		logTemplate{Message: "dont want"},
   331  	)
   332  
   333  	// Add 5 logs that should be returned.
   334  	want := logTemplate{Message: "want"}
   335  	s.writeLogsT(c, s.otherUUID, threshT, threshT.Add(5*time.Second), 5, want)
   336  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{StartTime: threshT}, s.oplogColl)
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	defer tailer.Stop()
   339  	s.assertTailer(c, tailer, 5, want)
   340  
   341  	// Write more logs. These will be read from the the oplog.
   342  	want2 := logTemplate{Message: "want 2"}
   343  	s.writeLogsT(c, s.otherUUID, threshT.Add(6*time.Second), threshT.Add(10*time.Second), 5, want2)
   344  	s.assertTailer(c, tailer, 5, want2)
   345  
   346  }
   347  
   348  func (s *LogTailerSuite) TestOplogTransition(c *gc.C) {
   349  	// Ensure that logs aren't repeated as the log tailer moves from
   350  	// reading from the logs collection to tailing the oplog.
   351  	//
   352  	// All logs are written out with the same timestamp to create a
   353  	// challenging scenario for the tailer.
   354  
   355  	for i := 0; i < 5; i++ {
   356  		s.writeLogs(c, s.otherUUID, 1, logTemplate{Message: strconv.Itoa(i)})
   357  	}
   358  
   359  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, s.oplogColl)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	defer tailer.Stop()
   362  	for i := 0; i < 5; i++ {
   363  		s.assertTailer(c, tailer, 1, logTemplate{Message: strconv.Itoa(i)})
   364  	}
   365  
   366  	// Write more logs. These will be read from the the oplog.
   367  	for i := 5; i < 10; i++ {
   368  		lt := logTemplate{Message: strconv.Itoa(i)}
   369  		s.writeLogs(c, s.otherUUID, 2, lt)
   370  		s.assertTailer(c, tailer, 2, lt)
   371  	}
   372  }
   373  
   374  func (s *LogTailerSuite) TestModelFiltering(c *gc.C) {
   375  	good := logTemplate{Message: "good"}
   376  	writeLogs := func() {
   377  		s.writeLogs(c, "someuuid0", 1, logTemplate{
   378  			Message: "bad",
   379  		})
   380  		s.writeLogs(c, "someuuid1", 1, logTemplate{
   381  			Message: "bad",
   382  		})
   383  		s.writeLogs(c, s.otherUUID, 1, good)
   384  	}
   385  
   386  	assert := func(tailer corelogger.LogTailer) {
   387  		// Only the entries the s.State's UUID should be reported.
   388  		s.assertTailer(c, tailer, 1, good)
   389  	}
   390  
   391  	s.checkLogTailerFiltering(c, s.otherState, corelogger.LogTailerParams{}, writeLogs, assert)
   392  }
   393  
   394  func (s *LogTailerSuite) TestTailingLogsOnlyForOneModel(c *gc.C) {
   395  	writeLogs := func() {
   396  		s.writeLogs(c, s.otherUUID, 1, logTemplate{
   397  			Message: "bad"},
   398  		)
   399  		s.writeLogs(c, s.modelUUID, 1, logTemplate{
   400  			Message: "good1",
   401  		})
   402  		s.writeLogs(c, s.modelUUID, 1, logTemplate{
   403  			Message: "good2",
   404  		})
   405  	}
   406  
   407  	assert := func(tailer corelogger.LogTailer) {
   408  		messages := map[string]bool{}
   409  		defer func() {
   410  			c.Assert(messages, gc.HasLen, 2)
   411  			for m := range messages {
   412  				if m != "good1" && m != "good2" {
   413  					c.Fatalf("received message: %v", m)
   414  				}
   415  			}
   416  		}()
   417  		count := 0
   418  		for {
   419  			select {
   420  			case log := <-tailer.Logs():
   421  				c.Assert(log.ModelUUID, gc.Equals, s.State.ModelUUID())
   422  				messages[log.Message] = true
   423  				count++
   424  				c.Logf("count %d", count)
   425  				if count >= 2 {
   426  					return
   427  				}
   428  			case <-time.After(coretesting.ShortWait):
   429  				c.Fatalf("timeout waiting for logs %d", count)
   430  			}
   431  		}
   432  	}
   433  	s.checkLogTailerFiltering(c, s.State, corelogger.LogTailerParams{}, writeLogs, assert)
   434  }
   435  
   436  func (s *LogTailerSuite) TestLevelFiltering(c *gc.C) {
   437  	info := logTemplate{Level: loggo.INFO}
   438  	error := logTemplate{Level: loggo.ERROR}
   439  	writeLogs := func() {
   440  		s.writeLogs(c, s.otherUUID, 1, logTemplate{Level: loggo.DEBUG})
   441  		s.writeLogs(c, s.otherUUID, 1, info)
   442  		s.writeLogs(c, s.otherUUID, 1, error)
   443  	}
   444  	params := corelogger.LogTailerParams{
   445  		MinLevel: loggo.INFO,
   446  	}
   447  	assert := func(tailer corelogger.LogTailer) {
   448  		s.assertTailer(c, tailer, 1, info)
   449  		s.assertTailer(c, tailer, 1, error)
   450  	}
   451  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   452  }
   453  
   454  func (s *LogTailerSuite) TestInitialLines(c *gc.C) {
   455  	expected := logTemplate{Message: "want"}
   456  	s.writeLogs(c, s.otherUUID, 3, logTemplate{Message: "dont want"})
   457  	s.writeLogs(c, s.otherUUID, 5, expected)
   458  
   459  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{InitialLines: 5}, nil)
   460  	c.Assert(err, jc.ErrorIsNil)
   461  	defer tailer.Stop()
   462  
   463  	// Should see just the last 5 lines as requested.
   464  	s.assertTailer(c, tailer, 5, expected)
   465  }
   466  
   467  func (s *LogTailerSuite) TestRecordsAddedOutOfTimeOrder(c *gc.C) {
   468  	format := "2006-01-02 03:04"
   469  	t1, err := time.Parse(format, "2016-11-25 09:10")
   470  	c.Assert(err, jc.ErrorIsNil)
   471  	t2, err := time.Parse(format, "2016-11-25 09:20")
   472  	c.Assert(err, jc.ErrorIsNil)
   473  	here := logTemplate{Message: "logged here"}
   474  	s.writeLogsT(c, s.otherUUID, t2, t2, 1, here)
   475  	migrated := logTemplate{Message: "transferred by migration"}
   476  	s.writeLogsT(c, s.otherUUID, t1, t1, 1, migrated)
   477  
   478  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{}, nil)
   479  	c.Assert(err, jc.ErrorIsNil)
   480  	defer tailer.Stop()
   481  
   482  	// They still come back in the right time order.
   483  	s.assertTailer(c, tailer, 1, migrated)
   484  	s.assertTailer(c, tailer, 1, here)
   485  }
   486  
   487  func (s *LogTailerSuite) TestInitialLinesWithNotEnoughLines(c *gc.C) {
   488  	expected := logTemplate{Message: "want"}
   489  	s.writeLogs(c, s.otherUUID, 2, expected)
   490  
   491  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{InitialLines: 5}, nil)
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	defer tailer.Stop()
   494  
   495  	// Should see just the 2 lines that existed, even though 5 were
   496  	// asked for.
   497  	s.assertTailer(c, tailer, 2, expected)
   498  }
   499  
   500  func (s *LogTailerSuite) TestNoTail(c *gc.C) {
   501  	expected := logTemplate{Message: "want"}
   502  	s.writeLogs(c, s.otherUUID, 2, expected)
   503  
   504  	// Write a log entry that's only in the oplog.
   505  	doc := s.logTemplateToDoc(logTemplate{Message: "dont want"}, coretesting.ZeroTime())
   506  	err := s.writeLogToOplog(s.otherUUID, doc)
   507  	c.Assert(err, jc.ErrorIsNil)
   508  
   509  	tailer, err := state.NewLogTailer(s.otherState, corelogger.LogTailerParams{NoTail: true}, nil)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	// Not strictly necessary, just in case NoTail doesn't work in the test.
   512  	defer tailer.Stop()
   513  
   514  	// Logs only in the oplog shouldn't be reported and the tailer
   515  	// should stop itself once the log collection has been read.
   516  	s.assertTailer(c, tailer, 2, expected)
   517  	select {
   518  	case _, ok := <-tailer.Logs():
   519  		if ok {
   520  			c.Fatal("shouldn't be any further logs")
   521  		}
   522  	case <-time.After(coretesting.LongWait):
   523  		c.Fatal("timed out waiting for logs channel to close")
   524  	}
   525  
   526  	select {
   527  	case <-tailer.Dying():
   528  		// Success.
   529  	case <-time.After(coretesting.LongWait):
   530  		c.Fatal("tailer didn't stop itself")
   531  	}
   532  }
   533  
   534  func (s *LogTailerSuite) TestIncludeEntity(c *gc.C) {
   535  	machine0 := logTemplate{Entity: "machine-0"}
   536  	foo0 := logTemplate{Entity: "unit-foo-0"}
   537  	foo1 := logTemplate{Entity: "unit-foo-1"}
   538  	writeLogs := func() {
   539  		s.writeLogs(c, s.otherUUID, 3, machine0)
   540  		s.writeLogs(c, s.otherUUID, 2, foo0)
   541  		s.writeLogs(c, s.otherUUID, 1, foo1)
   542  		s.writeLogs(c, s.otherUUID, 3, machine0)
   543  	}
   544  	params := corelogger.LogTailerParams{
   545  		IncludeEntity: []string{
   546  			"unit-foo-0",
   547  			"unit-foo-1",
   548  		},
   549  	}
   550  	assert := func(tailer corelogger.LogTailer) {
   551  		s.assertTailer(c, tailer, 2, foo0)
   552  		s.assertTailer(c, tailer, 1, foo1)
   553  	}
   554  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   555  }
   556  
   557  func (s *LogTailerSuite) TestIncludeEntityWildcard(c *gc.C) {
   558  	machine0 := logTemplate{Entity: "machine-0"}
   559  	foo0 := logTemplate{Entity: "unit-foo-0"}
   560  	foo1 := logTemplate{Entity: "unit-foo-1"}
   561  	writeLogs := func() {
   562  		s.writeLogs(c, s.otherUUID, 3, machine0)
   563  		s.writeLogs(c, s.otherUUID, 2, foo0)
   564  		s.writeLogs(c, s.otherUUID, 1, foo1)
   565  		s.writeLogs(c, s.otherUUID, 3, machine0)
   566  	}
   567  	params := corelogger.LogTailerParams{
   568  		IncludeEntity: []string{
   569  			"unit-foo*",
   570  		},
   571  	}
   572  	assert := func(tailer corelogger.LogTailer) {
   573  		s.assertTailer(c, tailer, 2, foo0)
   574  		s.assertTailer(c, tailer, 1, foo1)
   575  	}
   576  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   577  }
   578  
   579  func (s *LogTailerSuite) TestExcludeEntity(c *gc.C) {
   580  	machine0 := logTemplate{Entity: "machine-0"}
   581  	foo0 := logTemplate{Entity: "unit-foo-0"}
   582  	foo1 := logTemplate{Entity: "unit-foo-1"}
   583  	writeLogs := func() {
   584  		s.writeLogs(c, s.otherUUID, 3, machine0)
   585  		s.writeLogs(c, s.otherUUID, 2, foo0)
   586  		s.writeLogs(c, s.otherUUID, 1, foo1)
   587  		s.writeLogs(c, s.otherUUID, 3, machine0)
   588  	}
   589  	params := corelogger.LogTailerParams{
   590  		ExcludeEntity: []string{
   591  			"machine-0",
   592  			"unit-foo-0",
   593  		},
   594  	}
   595  	assert := func(tailer corelogger.LogTailer) {
   596  		s.assertTailer(c, tailer, 1, foo1)
   597  	}
   598  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   599  }
   600  
   601  func (s *LogTailerSuite) TestExcludeEntityWildcard(c *gc.C) {
   602  	machine0 := logTemplate{Entity: "machine-0"}
   603  	foo0 := logTemplate{Entity: "unit-foo-0"}
   604  	foo1 := logTemplate{Entity: "unit-foo-1"}
   605  	writeLogs := func() {
   606  		s.writeLogs(c, s.otherUUID, 3, machine0)
   607  		s.writeLogs(c, s.otherUUID, 2, foo0)
   608  		s.writeLogs(c, s.otherUUID, 1, foo1)
   609  		s.writeLogs(c, s.otherUUID, 3, machine0)
   610  	}
   611  	params := corelogger.LogTailerParams{
   612  		ExcludeEntity: []string{
   613  			"machine*",
   614  			"unit-*-0",
   615  		},
   616  	}
   617  	assert := func(tailer corelogger.LogTailer) {
   618  		s.assertTailer(c, tailer, 1, foo1)
   619  	}
   620  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   621  }
   622  
   623  func (s *LogTailerSuite) TestIncludeModule(c *gc.C) {
   624  	mod0 := logTemplate{Module: "foo.bar"}
   625  	mod1 := logTemplate{Module: "juju.thing"}
   626  	subMod1 := logTemplate{Module: "juju.thing.hai"}
   627  	mod2 := logTemplate{Module: "elsewhere"}
   628  	writeLogs := func() {
   629  		s.writeLogs(c, s.otherUUID, 1, mod0)
   630  		s.writeLogs(c, s.otherUUID, 1, mod1)
   631  		s.writeLogs(c, s.otherUUID, 1, mod0)
   632  		s.writeLogs(c, s.otherUUID, 1, subMod1)
   633  		s.writeLogs(c, s.otherUUID, 1, mod0)
   634  		s.writeLogs(c, s.otherUUID, 1, mod2)
   635  	}
   636  	params := corelogger.LogTailerParams{
   637  		IncludeModule: []string{"juju.thing", "elsewhere"},
   638  	}
   639  	assert := func(tailer corelogger.LogTailer) {
   640  		s.assertTailer(c, tailer, 1, mod1)
   641  		s.assertTailer(c, tailer, 1, subMod1)
   642  		s.assertTailer(c, tailer, 1, mod2)
   643  	}
   644  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   645  }
   646  
   647  func (s *LogTailerSuite) TestExcludeModule(c *gc.C) {
   648  	mod0 := logTemplate{Module: "foo.bar"}
   649  	mod1 := logTemplate{Module: "juju.thing"}
   650  	subMod1 := logTemplate{Module: "juju.thing.hai"}
   651  	mod2 := logTemplate{Module: "elsewhere"}
   652  	writeLogs := func() {
   653  		s.writeLogs(c, s.otherUUID, 1, mod0)
   654  		s.writeLogs(c, s.otherUUID, 1, mod1)
   655  		s.writeLogs(c, s.otherUUID, 1, mod0)
   656  		s.writeLogs(c, s.otherUUID, 1, subMod1)
   657  		s.writeLogs(c, s.otherUUID, 1, mod0)
   658  		s.writeLogs(c, s.otherUUID, 1, mod2)
   659  	}
   660  	params := corelogger.LogTailerParams{
   661  		ExcludeModule: []string{"juju.thing", "elsewhere"},
   662  	}
   663  	assert := func(tailer corelogger.LogTailer) {
   664  		s.assertTailer(c, tailer, 2, mod0)
   665  	}
   666  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   667  }
   668  
   669  func (s *LogTailerSuite) TestIncludeExcludeModule(c *gc.C) {
   670  	foo := logTemplate{Module: "foo"}
   671  	bar := logTemplate{Module: "bar"}
   672  	barSub := logTemplate{Module: "bar.thing"}
   673  	baz := logTemplate{Module: "baz"}
   674  	qux := logTemplate{Module: "qux"}
   675  	writeLogs := func() {
   676  		s.writeLogs(c, s.otherUUID, 1, foo)
   677  		s.writeLogs(c, s.otherUUID, 1, bar)
   678  		s.writeLogs(c, s.otherUUID, 1, barSub)
   679  		s.writeLogs(c, s.otherUUID, 1, baz)
   680  		s.writeLogs(c, s.otherUUID, 1, qux)
   681  	}
   682  	params := corelogger.LogTailerParams{
   683  		IncludeModule: []string{"foo", "bar", "qux"},
   684  		ExcludeModule: []string{"foo", "bar"},
   685  	}
   686  	assert := func(tailer corelogger.LogTailer) {
   687  		// Except just "qux" because "foo" and "bar" were included and
   688  		// then excluded.
   689  		s.assertTailer(c, tailer, 1, qux)
   690  	}
   691  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   692  }
   693  
   694  func (s *LogTailerSuite) TestIncludeLabels(c *gc.C) {
   695  	mod0 := logTemplate{Labels: []string{"foo_bar"}}
   696  	mod1 := logTemplate{Labels: []string{"juju_thing"}}
   697  	subMod1 := logTemplate{Labels: []string{"juju_thing_hai"}}
   698  	mod2 := logTemplate{Labels: []string{"elsewhere"}}
   699  	writeLogs := func() {
   700  		s.writeLogs(c, s.otherUUID, 1, mod0)
   701  		s.writeLogs(c, s.otherUUID, 1, mod1)
   702  		s.writeLogs(c, s.otherUUID, 1, mod0)
   703  		s.writeLogs(c, s.otherUUID, 1, subMod1)
   704  		s.writeLogs(c, s.otherUUID, 1, mod0)
   705  		s.writeLogs(c, s.otherUUID, 1, mod2)
   706  	}
   707  	params := corelogger.LogTailerParams{
   708  		IncludeLabel: []string{"juju_thing", "elsewhere"},
   709  	}
   710  	assert := func(tailer corelogger.LogTailer) {
   711  		s.assertTailer(c, tailer, 1, mod1)
   712  		s.assertTailer(c, tailer, 1, mod2)
   713  	}
   714  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   715  }
   716  
   717  func (s *LogTailerSuite) TestExcludeLabels(c *gc.C) {
   718  	mod0 := logTemplate{Labels: []string{"foo_bar"}}
   719  	mod1 := logTemplate{Labels: []string{"juju_thing"}}
   720  	subMod1 := logTemplate{Labels: []string{"juju_thing_hai"}}
   721  	mod2 := logTemplate{Labels: []string{"elsewhere"}}
   722  	writeLogs := func() {
   723  		s.writeLogs(c, s.otherUUID, 1, mod0)
   724  		s.writeLogs(c, s.otherUUID, 1, mod1)
   725  		s.writeLogs(c, s.otherUUID, 1, mod0)
   726  		s.writeLogs(c, s.otherUUID, 1, subMod1)
   727  		s.writeLogs(c, s.otherUUID, 1, mod0)
   728  		s.writeLogs(c, s.otherUUID, 1, mod2)
   729  	}
   730  	params := corelogger.LogTailerParams{
   731  		ExcludeLabel: []string{"juju_thing", "juju_thing_hai", "elsewhere"},
   732  	}
   733  	assert := func(tailer corelogger.LogTailer) {
   734  		s.assertTailer(c, tailer, 2, mod0)
   735  	}
   736  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   737  }
   738  
   739  func (s *LogTailerSuite) TestIncludeExcludeLabels(c *gc.C) {
   740  	foo := logTemplate{Labels: []string{"foo"}}
   741  	bar := logTemplate{Labels: []string{"bar"}}
   742  	barSub := logTemplate{Labels: []string{"bar_thing"}}
   743  	baz := logTemplate{Labels: []string{"baz"}}
   744  	qux := logTemplate{Labels: []string{"qux"}}
   745  	writeLogs := func() {
   746  		s.writeLogs(c, s.otherUUID, 1, foo)
   747  		s.writeLogs(c, s.otherUUID, 1, bar)
   748  		s.writeLogs(c, s.otherUUID, 1, barSub)
   749  		s.writeLogs(c, s.otherUUID, 1, baz)
   750  		s.writeLogs(c, s.otherUUID, 1, qux)
   751  	}
   752  	params := corelogger.LogTailerParams{
   753  		IncludeLabel: []string{"foo", "bar", "qux"},
   754  		ExcludeLabel: []string{"foo", "bar"},
   755  	}
   756  	assert := func(tailer corelogger.LogTailer) {
   757  		// Except just "qux" because "foo" and "bar" were included and
   758  		// then excluded.
   759  		s.assertTailer(c, tailer, 1, qux)
   760  	}
   761  	s.checkLogTailerFiltering(c, s.otherState, params, writeLogs, assert)
   762  }
   763  
   764  func (s *LogTailerSuite) checkLogTailerFiltering(
   765  	c *gc.C,
   766  	st *state.State,
   767  	params corelogger.LogTailerParams,
   768  	writeLogs func(),
   769  	assertTailer func(corelogger.LogTailer),
   770  ) {
   771  	// Check the tailer does the right thing when reading from the
   772  	// logs collection.
   773  	writeLogs()
   774  	tailer, err := state.NewLogTailer(st, params, s.oplogColl)
   775  	c.Assert(err, jc.ErrorIsNil)
   776  	defer tailer.Stop()
   777  	assertTailer(tailer)
   778  
   779  	// Now write out logs and check the tailer again. These will be
   780  	// read from the oplog.
   781  	writeLogs()
   782  	assertTailer(tailer)
   783  }
   784  
   785  type logTemplate struct {
   786  	Entity   string
   787  	Version  version.Number
   788  	Module   string
   789  	Location string
   790  	Level    loggo.Level
   791  	Message  string
   792  	Labels   []string
   793  }
   794  
   795  // writeLogs creates count log messages at the current time using
   796  // the supplied template. As well as writing to the logs collection,
   797  // entries are also made into the fake oplog collection.
   798  func (s *LogTailerSuite) writeLogs(c *gc.C, modelUUID string, count int, lt logTemplate) {
   799  	t := coretesting.ZeroTime()
   800  	s.writeLogsT(c, modelUUID, t, t, count, lt)
   801  }
   802  
   803  // writeLogsT creates count log messages between startTime and
   804  // endTime using the supplied template. As well as writing to the logs
   805  // collection, entries are also made into the fake oplog collection.
   806  func (s *LogTailerSuite) writeLogsT(c *gc.C, modelUUID string, startTime, endTime time.Time, count int, lt logTemplate) {
   807  	interval := endTime.Sub(startTime) / time.Duration(count)
   808  	t := startTime
   809  	for i := 0; i < count; i++ {
   810  		doc := s.logTemplateToDoc(lt, t)
   811  		err := s.writeLogToOplog(modelUUID, doc)
   812  		c.Assert(err, jc.ErrorIsNil)
   813  		err = s.getCollection(modelUUID).Insert(doc)
   814  		c.Assert(err, jc.ErrorIsNil)
   815  		t = t.Add(interval)
   816  	}
   817  }
   818  
   819  // writeLogToOplog writes out a log record to the a (probably fake)
   820  // oplog collection.
   821  func (s *LogTailerSuite) writeLogToOplog(modelUUID string, doc interface{}) error {
   822  	return s.oplogColl.Insert(bson.D{
   823  		{"ts", bson.MongoTimestamp(coretesting.ZeroTime().Unix() << 32)}, // an approximation which will do
   824  		{"h", rand.Int63()}, // again, a suitable fake
   825  		{"op", "i"},         // this will always be an insert
   826  		{"ns", "logs.logs." + modelUUID},
   827  		{"o", doc},
   828  	})
   829  }
   830  
   831  // deleteLogOplogEntry writes out a log record to the a (probably fake)
   832  // oplog collection.
   833  func (s *LogTailerSuite) deleteLogOplogEntry(modelUUID string, doc interface{}) error {
   834  	return s.oplogColl.Insert(bson.D{
   835  		{"ts", bson.MongoTimestamp(coretesting.ZeroTime().Unix() << 32)}, // an approximation which will do
   836  		{"h", rand.Int63()}, // again, a suitable fake
   837  		{"op", "d"},
   838  		{"ns", "logs.logs." + modelUUID},
   839  		{"o", doc},
   840  	})
   841  }
   842  
   843  func (s *LogTailerSuite) normaliseLogTemplate(lt *logTemplate) {
   844  	if lt.Entity == "" {
   845  		lt.Entity = "not-a-tag"
   846  	}
   847  	if lt.Version == version.Zero {
   848  		lt.Version = jujuversion.Current
   849  	}
   850  	if lt.Module == "" {
   851  		lt.Module = "module"
   852  	}
   853  	if lt.Location == "" {
   854  		lt.Location = "loc"
   855  	}
   856  	if lt.Level == loggo.UNSPECIFIED {
   857  		lt.Level = loggo.INFO
   858  	}
   859  	if lt.Message == "" {
   860  		lt.Message = "message"
   861  	}
   862  }
   863  
   864  func (s *LogTailerSuite) logTemplateToDoc(lt logTemplate, t time.Time) interface{} {
   865  	s.normaliseLogTemplate(&lt)
   866  	return state.MakeLogDoc(
   867  		lt.Entity,
   868  		t,
   869  		lt.Module,
   870  		lt.Location,
   871  		lt.Level,
   872  		lt.Message,
   873  		lt.Labels,
   874  	)
   875  }
   876  
   877  func (s *LogTailerSuite) assertTailer(c *gc.C, tailer corelogger.LogTailer, expectedCount int, lt logTemplate) {
   878  	s.normaliseLogTemplate(&lt)
   879  
   880  	timeout := time.After(coretesting.LongWait)
   881  	count := 0
   882  	for {
   883  		select {
   884  		case log, ok := <-tailer.Logs():
   885  			if !ok {
   886  				c.Fatalf("tailer died unexpectedly: %v", tailer.Err())
   887  			}
   888  
   889  			c.Assert(log.Version, gc.Equals, lt.Version)
   890  			c.Assert(log.Entity, gc.Equals, lt.Entity)
   891  			c.Assert(log.Module, gc.Equals, lt.Module)
   892  			c.Assert(log.Location, gc.Equals, lt.Location)
   893  			c.Assert(log.Level, gc.Equals, lt.Level)
   894  			c.Assert(log.Message, gc.Equals, lt.Message)
   895  			c.Assert(log.Labels, gc.DeepEquals, lt.Labels)
   896  			count++
   897  			if count == expectedCount {
   898  				return
   899  			}
   900  		case <-timeout:
   901  			c.Fatalf("timed out waiting for logs (received %d)", count)
   902  		}
   903  	}
   904  }
   905  
   906  type DBLogSizeSuite struct {
   907  	coretesting.BaseSuite
   908  }
   909  
   910  var _ = gc.Suite(&DBLogSizeSuite{})
   911  
   912  func (*DBLogSizeSuite) TestDBLogSizeIntSize(c *gc.C) {
   913  	res, err := state.DBCollectionSizeToInt(bson.M{"size": int(12345)}, "coll-name")
   914  	c.Assert(err, jc.ErrorIsNil)
   915  	c.Check(res, gc.Equals, int(12345))
   916  }
   917  
   918  func (*DBLogSizeSuite) TestDBLogSizeNoSize(c *gc.C) {
   919  	res, err := state.DBCollectionSizeToInt(bson.M{}, "coll-name")
   920  	// Old code didn't treat this as an error, if we know it doesn't happen often, we could start changing it to be an error.
   921  	c.Assert(err, jc.ErrorIsNil)
   922  	c.Check(res, gc.Equals, int(0))
   923  }
   924  
   925  func (*DBLogSizeSuite) TestDBLogSizeInt64Size(c *gc.C) {
   926  	// Production results have shown that sometimes collStats can return an int64.
   927  	// See https://bugs.launchpad.net/juju/+bug/1790626 in case we ever figure out why
   928  	res, err := state.DBCollectionSizeToInt(bson.M{"size": int64(12345)}, "coll-name")
   929  	c.Assert(err, jc.ErrorIsNil)
   930  	c.Check(res, gc.Equals, int(12345))
   931  }
   932  
   933  func (*DBLogSizeSuite) TestDBLogSizeInt64SizeOverflow(c *gc.C) {
   934  	// Just in case, it is unlikely this ever actually happens
   935  	res, err := state.DBCollectionSizeToInt(bson.M{"size": int64(12345678901)}, "coll-name")
   936  	c.Assert(err, jc.ErrorIsNil)
   937  	c.Check(res, gc.Equals, int((1<<31)-1))
   938  }
   939  
   940  func (*DBLogSizeSuite) TestDBLogSizeNegativeSize(c *gc.C) {
   941  	_, err := state.DBCollectionSizeToInt(bson.M{"size": int(-10)}, "coll-name")
   942  	c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" returned a negative value: -10`)
   943  	_, err = state.DBCollectionSizeToInt(bson.M{"size": int64(-10)}, "coll-name")
   944  	c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" returned a negative value: -10`)
   945  }
   946  
   947  func (*DBLogSizeSuite) TestDBLogSizeUnknownType(c *gc.C) {
   948  	_, err := state.DBCollectionSizeToInt(bson.M{"size": float64(12345)}, "coll-name")
   949  	c.Check(err, gc.ErrorMatches, `mongo collStats for "coll-name" did not return an int or int64 for size, returned float64: 12345`)
   950  }