github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/presence/pruner_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"sort"
    10  	"sync"
    11  	"time"
    12  
    13  	gitjujutesting "github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/names.v2"
    18  	"gopkg.in/juju/worker.v1"
    19  	"gopkg.in/mgo.v2"
    20  
    21  	"github.com/juju/juju/testing"
    22  )
    23  
    24  type prunerSuite struct {
    25  	gitjujutesting.MgoSuite
    26  	testing.BaseSuite
    27  	presence *mgo.Collection
    28  	beings   *mgo.Collection
    29  	pings    *mgo.Collection
    30  	modelTag names.ModelTag
    31  }
    32  
    33  var _ = gc.Suite(&prunerSuite{})
    34  
    35  func (s *prunerSuite) SetUpSuite(c *gc.C) {
    36  	s.BaseSuite.SetUpSuite(c)
    37  	s.MgoSuite.SetUpSuite(c)
    38  	uuid, err := utils.NewUUID()
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	s.modelTag = names.NewModelTag(uuid.String())
    41  }
    42  
    43  func (s *prunerSuite) TearDownSuite(c *gc.C) {
    44  	s.MgoSuite.TearDownSuite(c)
    45  	s.BaseSuite.TearDownSuite(c)
    46  }
    47  
    48  func (s *prunerSuite) SetUpTest(c *gc.C) {
    49  	s.BaseSuite.SetUpTest(c)
    50  	s.MgoSuite.SetUpTest(c)
    51  
    52  	db := s.MgoSuite.Session.DB("presence")
    53  	s.presence = db.C("presence")
    54  	s.beings = db.C("presence.beings")
    55  	s.pings = db.C("presence.pings")
    56  
    57  	FakeTimeSlot(0)
    58  }
    59  
    60  func (s *prunerSuite) TearDownTest(c *gc.C) {
    61  	s.MgoSuite.TearDownTest(c)
    62  	s.BaseSuite.TearDownTest(c)
    63  
    64  	RealTimeSlot()
    65  	RealPeriod()
    66  }
    67  
    68  func findBeing(c *gc.C, beingsC *mgo.Collection, modelUUID string, seq int64) (beingInfo, error) {
    69  	var being beingInfo
    70  	err := beingsC.FindId(docIDInt64(modelUUID, seq)).One(&being)
    71  	return being, err
    72  }
    73  
    74  func checkCollectionCount(c *gc.C, coll *mgo.Collection, count int) {
    75  	count, err := coll.Count()
    76  	c.Assert(err, jc.ErrorIsNil)
    77  	c.Check(count, gc.Equals, count)
    78  }
    79  
    80  func (s *prunerSuite) getDirectRecorder() PingRecorder {
    81  	return DirectRecordFunc(s.presence)
    82  }
    83  
    84  func (s *prunerSuite) TestPrunesOldPingsAndBeings(c *gc.C) {
    85  	keys := []string{"key1", "key2"}
    86  	pingers := make([]*Pinger, len(keys))
    87  	for i, key := range keys {
    88  		pingers[i] = NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder)
    89  	}
    90  	const numSlots = 10
    91  	sequences := make([][]int64, len(keys))
    92  	for i := range keys {
    93  		sequences[i] = make([]int64, numSlots)
    94  	}
    95  
    96  	for i := 0; i < numSlots; i++ {
    97  		FakeTimeSlot(i)
    98  		for j, p := range pingers {
    99  			// Create a new being sequence, and force a ping in this
   100  			// time slot. We don't Start()/Stop() them so we don't
   101  			// have to worry about things being async.
   102  			p.prepare()
   103  			p.ping()
   104  			sequences[j][i] = p.beingSeq
   105  		}
   106  	}
   107  	// At this point, we should have 10 ping slots active, and 10 different
   108  	// beings representing each key
   109  	checkCollectionCount(c, s.beings, numSlots*len(keys))
   110  	checkCollectionCount(c, s.pings, numSlots)
   111  	// Now we prune them, and assert that it removed items, but preserved the
   112  	// latest beings (things referenced by the latest pings)
   113  	pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0)
   114  	c.Assert(pruner.Prune(nil), jc.ErrorIsNil)
   115  	checkCollectionCount(c, s.pings, 4)
   116  	checkCollectionCount(c, s.beings, 2*len(keys))
   117  	for i, key := range keys {
   118  		expectedSeq := sequences[i][numSlots-2]
   119  		being, err := findBeing(c, s.beings, s.modelTag.Id(), expectedSeq)
   120  		c.Assert(err, jc.ErrorIsNil)
   121  		c.Check(being.Seq, gc.Equals, expectedSeq)
   122  		c.Check(being.Key, gc.Equals, key)
   123  	}
   124  }
   125  
   126  func (s *prunerSuite) TestPreservesLatestSequence(c *gc.C) {
   127  	FakePeriod(1)
   128  
   129  	key := "blah"
   130  	p1 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder)
   131  	p1.Start()
   132  	assertStopped(c, p1)
   133  	p2 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder)
   134  	p2.Start()
   135  	assertStopped(c, p2)
   136  	// we're starting p2 second, so it should get a higher sequence
   137  	c.Check(p1.beingSeq, gc.Not(gc.Equals), int64(0))
   138  	c.Check(p1.beingSeq, jc.LessThan, p2.beingSeq)
   139  	// Before pruning, we expect both beings to exist
   140  	being, err := findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq)
   141  	c.Assert(err, jc.ErrorIsNil)
   142  	c.Check(being.Key, gc.Equals, key)
   143  	c.Check(being.Seq, gc.Equals, p1.beingSeq)
   144  	being, err = findBeing(c, s.beings, s.modelTag.Id(), p2.beingSeq)
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	c.Check(being.Key, gc.Equals, key)
   147  	c.Check(being.Seq, gc.Equals, p2.beingSeq)
   148  
   149  	pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0)
   150  	c.Assert(pruner.Prune(nil), jc.ErrorIsNil)
   151  	// After pruning, p2 should still be available
   152  	being, err = findBeing(c, s.beings, s.modelTag.Id(), p2.beingSeq)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  	c.Check(being.Seq, gc.Equals, p2.beingSeq)
   155  }
   156  
   157  func (s *prunerSuite) TestMultiplePingersClearMemoryCache(c *gc.C) {
   158  	FakePeriod(1)
   159  
   160  	key := "blah"
   161  	p1 := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder)
   162  	p1.Start()
   163  	assertStopped(c, p1)
   164  	highestSeq := p1.beingSeq
   165  	memCache := make(map[int64]string)
   166  	memCache[p1.beingSeq] = key
   167  	for i := 1; i < 10; i++ {
   168  		FakeTimeSlot(i)
   169  		newP := NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder)
   170  		newP.Start()
   171  		assertStopped(c, newP)
   172  		highestSeq = newP.beingSeq
   173  		memCache[highestSeq] = key
   174  	}
   175  	// Before pruning, we expect the first and last beings to exist
   176  	being, err := findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq)
   177  	c.Assert(err, jc.ErrorIsNil)
   178  	c.Check(being.Key, gc.Equals, key)
   179  	c.Check(being.Seq, gc.Equals, p1.beingSeq)
   180  	being, err = findBeing(c, s.beings, s.modelTag.Id(), highestSeq)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	c.Check(being.Key, gc.Equals, key)
   183  	c.Check(being.Seq, gc.Equals, highestSeq)
   184  
   185  	c.Check(memCache[p1.beingSeq], gc.Equals, key)
   186  	c.Check(memCache[highestSeq], gc.Equals, key)
   187  	pruner := NewPruner(s.modelTag.Id(), s.beings, s.pings, 0)
   188  	c.Assert(pruner.Prune(memCache), jc.ErrorIsNil)
   189  	// The oldest should no longer be available, but the latest should
   190  	// And the old seq should be pruned from the memory cache
   191  	being, err = findBeing(c, s.beings, s.modelTag.Id(), highestSeq)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	c.Check(memCache[highestSeq], gc.Equals, key)
   194  	being, err = findBeing(c, s.beings, s.modelTag.Id(), p1.beingSeq)
   195  	c.Assert(err, gc.ErrorMatches, "not found")
   196  	// Not found
   197  	c.Check(memCache[p1.beingSeq], gc.Equals, "")
   198  }
   199  
   200  func waitForFirstChange(c *gc.C, watch <-chan Change, want Change) {
   201  	timeout := time.After(testing.LongWait)
   202  	for {
   203  		select {
   204  		case got := <-watch:
   205  			if got == want {
   206  				return
   207  			}
   208  			if got.Alive == false {
   209  				c.Fatalf("got a not-alive before the one we were expecting: %v (want %v)", got, want)
   210  			}
   211  		case <-timeout:
   212  			c.Fatalf("watch reported nothing, want %v", want)
   213  		}
   214  	}
   215  }
   216  
   217  // assertStopped stops a worker and waits until it reports stopped.
   218  // Use this method in favor of defer w.Stop() because you _must_ ensure
   219  // that the worker has stopped, and thus is no longer using its mgo
   220  // session before TearDownTest shuts down the connection.
   221  func assertStopped(c *gc.C, w worker.Worker) {
   222  	done := make(chan struct{})
   223  	go func() {
   224  		c.Check(worker.Stop(w), jc.ErrorIsNil)
   225  		close(done)
   226  	}()
   227  	select {
   228  	case <-done:
   229  		return
   230  	case <-time.After(testing.ShortWait):
   231  		c.Fatalf("failed to stop worker %v after %v", w, testing.ShortWait)
   232  	}
   233  }
   234  
   235  func (s *prunerSuite) TestDeepStressStaysSane(c *gc.C) {
   236  	FakePeriod(2)
   237  	keys := make([]string, 50)
   238  	for i := 0; i < len(keys); i++ {
   239  		keys[i] = fmt.Sprintf("being-%04d", i)
   240  	}
   241  	// To create abuse on the system, we leave 2 pingers active for every
   242  	// key. We then keep creating new pingers for each one, and rotate them
   243  	// into old, and stop them when they rotate out. So we potentially have
   244  	// 3 active pingers for each key. We should never see any key go
   245  	// inactive, because we ping when we start a Pinger.
   246  	oldPingers := make([]*Pinger, len(keys))
   247  	newPingers := make([]*Pinger, len(keys))
   248  	ch := make(chan Change)
   249  	w := NewWatcher(s.presence, s.modelTag)
   250  	// Ensure that all pingers and the watcher are clean at exit
   251  	defer assertStopped(c, w)
   252  	pb := NewPingBatcher(s.presence, 500*time.Millisecond)
   253  	defer assertStopped(c, pb)
   254  	getPB := func() PingRecorder { return pb }
   255  	defer func() {
   256  		for i, p := range oldPingers {
   257  			if p == nil {
   258  				continue
   259  			}
   260  			assertStopped(c, p)
   261  			oldPingers[i] = nil
   262  		}
   263  		for i, p := range newPingers {
   264  			if p == nil {
   265  				continue
   266  			}
   267  			assertStopped(c, p)
   268  			newPingers[i] = nil
   269  		}
   270  	}()
   271  	t := time.Now()
   272  	FakeTimeSlot(1)
   273  	for i, key := range keys {
   274  		w.Watch(key, ch)
   275  		// we haven't started the pinger yet, so the initial state must be stopped
   276  		// As this is a busy channel, we may be queued up behind some other
   277  		// pinger showing up as alive, so allow up to LongWait for the event to show up
   278  		waitForFirstChange(c, ch, Change{key, false})
   279  		p := NewPinger(s.presence, s.modelTag, key, getPB)
   280  		err := p.Start()
   281  		c.Assert(err, jc.ErrorIsNil)
   282  		newPingers[i] = p
   283  	}
   284  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   285  	c.Logf("initialized %d pingers in %v\n", len(newPingers), time.Since(t))
   286  	// Make sure all of the entities stay showing up as alive
   287  	deadKeys := make([]string, 0)
   288  	done := make(chan struct{})
   289  	var wg sync.WaitGroup
   290  	wg.Add(1)
   291  	go func() {
   292  		for {
   293  			select {
   294  			case got := <-ch:
   295  				if !got.Alive {
   296  					deadKeys = append(deadKeys, got.Key)
   297  				}
   298  			case <-done:
   299  				wg.Done()
   300  				return
   301  			}
   302  		}
   303  	}()
   304  	beings := s.presence.Database.C(s.presence.Name + ".beings")
   305  	// Create a background Pruner task, that prunes items independently of
   306  	// when they are being updated
   307  	wg.Add(1)
   308  	go func() {
   309  		for {
   310  			select {
   311  			case <-done:
   312  				wg.Done()
   313  				return
   314  			case <-time.After(time.Duration(rand.Intn(500)+1000) * time.Millisecond):
   315  				oldPruner := NewPruner(s.modelTag.Id(), beings, s.pings, 0)
   316  				// Don't assert in a goroutine, as the panic may do bad things
   317  				c.Check(oldPruner.Prune(nil), jc.ErrorIsNil)
   318  			}
   319  		}
   320  	}()
   321  	const loopCount = 10
   322  	for loop := 0; loop < loopCount; loop++ {
   323  		FakeTimeSlot(loop + 2)
   324  		t := time.Now()
   325  		for _, i := range rand.Perm(len(keys)) {
   326  			old := oldPingers[i]
   327  			if old != nil {
   328  				assertStopped(c, old)
   329  			}
   330  			oldPingers[i] = newPingers[i]
   331  			p := NewPinger(s.presence, s.modelTag, keys[i], getPB)
   332  			err := p.Start()
   333  			c.Assert(err, jc.ErrorIsNil)
   334  			newPingers[i] = p
   335  		}
   336  		c.Assert(pb.Sync(), jc.ErrorIsNil)
   337  		c.Logf("loop %d in %v\n", loop, time.Since(t))
   338  	}
   339  	// Now that we've gone through all of that, check that we've created as
   340  	// many sequences as we think we have
   341  	seq := s.presence.Database.C(s.presence.Name + ".seqs")
   342  	var sequence struct {
   343  		Seq int64 `bson:"seq"`
   344  	}
   345  	seqDocID := s.modelTag.Id() + ":beings"
   346  	err := seq.FindId(seqDocID).One(&sequence)
   347  	c.Assert(err, jc.ErrorIsNil)
   348  	// we should have created N keys Y+1 times (once in init, once per loop)
   349  	seqCount := int64(len(keys) * (loopCount + 1))
   350  	c.Check(sequence.Seq, gc.Equals, seqCount)
   351  	oldPruner := NewPruner(s.modelTag.Id(), beings, s.pings, 0)
   352  	c.Assert(oldPruner.Prune(nil), jc.ErrorIsNil)
   353  	count, err := beings.Count()
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	// After pruning, we should have at least one sequence for each key,
   356  	// but not more than fits in the last 4 ping slots
   357  	c.Check(count, jc.GreaterThan, len(keys)-1)
   358  	c.Check(count, jc.LessThan, len(keys)*8)
   359  	// Run the pruner again, it should essentially be a no-op
   360  	oldPruner = NewPruner(s.modelTag.Id(), beings, s.pings, 0)
   361  	c.Assert(oldPruner.Prune(nil), jc.ErrorIsNil)
   362  	close(done)
   363  	wg.Wait()
   364  	sort.Strings(deadKeys)
   365  	c.Check(len(deadKeys), gc.Equals, 0)
   366  	c.Check(deadKeys, jc.DeepEquals, []string{})
   367  }