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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package presence_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	gitjujutesting "github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/juju/utils"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/names.v2"
    15  	"gopkg.in/mgo.v2"
    16  	"gopkg.in/mgo.v2/bson"
    17  
    18  	"github.com/juju/juju/state/presence"
    19  	"github.com/juju/juju/testing"
    20  )
    21  
    22  type PingBatcherSuite struct {
    23  	gitjujutesting.MgoSuite
    24  	testing.BaseSuite
    25  	presence *mgo.Collection
    26  	pings    *mgo.Collection
    27  	modelTag names.ModelTag
    28  }
    29  
    30  var _ = gc.Suite(&PingBatcherSuite{})
    31  
    32  func (s *PingBatcherSuite) SetUpSuite(c *gc.C) {
    33  	s.BaseSuite.SetUpSuite(c)
    34  	s.MgoSuite.SetUpSuite(c)
    35  	uuid, err := utils.NewUUID()
    36  	c.Assert(err, jc.ErrorIsNil)
    37  	s.modelTag = names.NewModelTag(uuid.String())
    38  }
    39  
    40  func (s *PingBatcherSuite) TearDownSuite(c *gc.C) {
    41  	s.MgoSuite.TearDownSuite(c)
    42  	s.BaseSuite.TearDownSuite(c)
    43  }
    44  
    45  func (s *PingBatcherSuite) SetUpTest(c *gc.C) {
    46  	s.BaseSuite.SetUpTest(c)
    47  	s.MgoSuite.SetUpTest(c)
    48  
    49  	db := s.MgoSuite.Session.DB("presence")
    50  	s.presence = db.C("presence")
    51  	s.pings = db.C("presence.pings")
    52  
    53  	presence.FakeTimeSlot(0)
    54  }
    55  
    56  func (s *PingBatcherSuite) TearDownTest(c *gc.C) {
    57  	s.MgoSuite.TearDownTest(c)
    58  	s.BaseSuite.TearDownTest(c)
    59  
    60  	presence.RealTimeSlot()
    61  	presence.RealPeriod()
    62  }
    63  
    64  func (s *PingBatcherSuite) assertPingsRecorded(c *gc.C, pb *presence.PingBatcher) {
    65  	// UnixNano time rounded to 30s interval
    66  	slot := int64(1497960150)
    67  	c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil)
    68  	c.Assert(pb.Ping("test-uuid", slot, "0", 16), jc.ErrorIsNil)
    69  	c.Assert(pb.Ping("test-uuid", slot, "1", 128), jc.ErrorIsNil)
    70  	c.Assert(pb.Ping("test-uuid", slot, "1", 1), jc.ErrorIsNil)
    71  	c.Assert(pb.Sync(), jc.ErrorIsNil)
    72  	docId := "test-uuid:1497960150"
    73  	var res bson.M
    74  	c.Assert(s.pings.FindId(docId).One(&res), jc.ErrorIsNil)
    75  	c.Check(res["slot"], gc.Equals, slot)
    76  	c.Check(res["alive"], jc.DeepEquals, bson.M{
    77  		"0": int64(24),
    78  		"1": int64(129),
    79  	})
    80  }
    81  func (s *PingBatcherSuite) TestRecordsPings(c *gc.C) {
    82  	pb := presence.NewPingBatcher(s.presence, time.Second)
    83  	defer assertStopped(c, pb)
    84  	s.assertPingsRecorded(c, pb)
    85  }
    86  
    87  func (s *PingBatcherSuite) TestRecordsPingsUsingInc(c *gc.C) {
    88  	pb := presence.NewPingBatcher(s.presence, time.Second)
    89  	pb.ForceUpdatesUsingInc()
    90  	defer assertStopped(c, pb)
    91  	s.assertPingsRecorded(c, pb)
    92  }
    93  
    94  func (s *PingBatcherSuite) TestMultipleUUIDs(c *gc.C) {
    95  	pb := presence.NewPingBatcher(s.presence, time.Second)
    96  	defer assertStopped(c, pb)
    97  
    98  	// UnixNano time rounded to 30s interval
    99  	slot := int64(1497960150)
   100  	uuid1 := "test-uuid1"
   101  	uuid2 := "test-uuid2"
   102  	c.Assert(pb.Ping(uuid1, slot, "0", 8), jc.ErrorIsNil)
   103  	c.Assert(pb.Ping(uuid2, slot, "0", 8), jc.ErrorIsNil)
   104  	c.Assert(pb.Ping(uuid2, slot, "0", 4), jc.ErrorIsNil)
   105  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   106  	docId1 := fmt.Sprintf("%s:%d", uuid1, slot)
   107  	var res bson.M
   108  	c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil)
   109  	c.Check(res["slot"], gc.Equals, slot)
   110  	c.Check(res["alive"], jc.DeepEquals, bson.M{
   111  		"0": int64(8),
   112  	})
   113  	docId2 := fmt.Sprintf("%s:%d", uuid2, slot)
   114  	c.Assert(s.pings.FindId(docId2).One(&res), jc.ErrorIsNil)
   115  	c.Check(res["slot"], gc.Equals, slot)
   116  	c.Check(res["alive"], jc.DeepEquals, bson.M{
   117  		"0": int64(12),
   118  	})
   119  }
   120  
   121  func (s *PingBatcherSuite) TestMultipleFlushes(c *gc.C) {
   122  	pb := presence.NewPingBatcher(s.presence, time.Second)
   123  	defer assertStopped(c, pb)
   124  
   125  	slot := int64(1497960150)
   126  	uuid1 := "test-uuid1"
   127  	c.Assert(pb.Ping(uuid1, slot, "0", 8), jc.ErrorIsNil)
   128  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   129  
   130  	docId1 := fmt.Sprintf("%s:%d", uuid1, slot)
   131  	var res bson.M
   132  	c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil)
   133  	c.Check(res, gc.DeepEquals, bson.M{
   134  		"_id":  docId1,
   135  		"slot": slot,
   136  		"alive": bson.M{
   137  			"0": int64(8),
   138  		},
   139  	})
   140  
   141  	c.Assert(pb.Ping(uuid1, slot, "0", 1024), jc.ErrorIsNil)
   142  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   143  	c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil)
   144  	c.Check(res, gc.DeepEquals, bson.M{
   145  		"_id":  docId1,
   146  		"slot": slot,
   147  		"alive": bson.M{
   148  			"0": int64(1032),
   149  		},
   150  	})
   151  }
   152  
   153  func (s *PingBatcherSuite) TestMultipleSlots(c *gc.C) {
   154  	pb := presence.NewPingBatcher(s.presence, time.Second)
   155  	defer assertStopped(c, pb)
   156  
   157  	slot1 := int64(1497960150)
   158  	slot2 := int64(1497960180)
   159  	uuid1 := "test-uuid1"
   160  	c.Assert(pb.Ping(uuid1, slot1, "0", 8), jc.ErrorIsNil)
   161  	c.Assert(pb.Ping(uuid1, slot1, "0", 32), jc.ErrorIsNil)
   162  	c.Assert(pb.Ping(uuid1, slot2, "1", 16), jc.ErrorIsNil)
   163  	c.Assert(pb.Ping(uuid1, slot2, "0", 8), jc.ErrorIsNil)
   164  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   165  
   166  	docId1 := fmt.Sprintf("%s:%d", uuid1, slot1)
   167  	var res bson.M
   168  	c.Assert(s.pings.FindId(docId1).One(&res), jc.ErrorIsNil)
   169  	c.Check(res, gc.DeepEquals, bson.M{
   170  		"_id":  docId1,
   171  		"slot": slot1,
   172  		"alive": bson.M{
   173  			"0": int64(40),
   174  		},
   175  	})
   176  
   177  	docId2 := fmt.Sprintf("%s:%d", uuid1, slot2)
   178  	c.Assert(s.pings.FindId(docId2).One(&res), jc.ErrorIsNil)
   179  	c.Check(res["slot"], gc.Equals, slot2)
   180  	c.Check(res, gc.DeepEquals, bson.M{
   181  		"_id":  docId2,
   182  		"slot": slot2,
   183  		"alive": bson.M{
   184  			"0": int64(8),
   185  			"1": int64(16),
   186  		},
   187  	})
   188  }
   189  
   190  func (s *PingBatcherSuite) TestDocBatchSize(c *gc.C) {
   191  	// We don't want to hit an internal flush
   192  	pb := presence.NewPingBatcher(s.presence, time.Hour)
   193  	defer assertStopped(c, pb)
   194  
   195  	slotBase := int64(1497960150)
   196  	fieldKey := "0"
   197  	fieldBit := uint64(64)
   198  	// 100 slots * 100 models should be 10,000 docs that we are inserting.
   199  	// mgo.Bulk fails if you try to do more than 1000 requests at once, so this would trigger it if we didn't batch properly.
   200  	for modelCounter := 0; modelCounter < 100; modelCounter++ {
   201  		for slotOffset := 0; slotOffset < 100; slotOffset++ {
   202  			slot := slotBase + int64(slotOffset*30)
   203  			uuid := fmt.Sprintf("uuid-%d", modelCounter)
   204  			c.Assert(pb.Ping(uuid, slot, fieldKey, fieldBit), jc.ErrorIsNil)
   205  		}
   206  	}
   207  	c.Assert(pb.Sync(), jc.ErrorIsNil)
   208  	count, err := s.pings.Count()
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	c.Check(count, gc.Equals, 100*100)
   211  }
   212  
   213  func (s *PingBatcherSuite) TestBatchFlushesByTime(c *gc.C) {
   214  	t := time.Now()
   215  	pb := presence.NewPingBatcher(s.presence, 50*time.Millisecond)
   216  	defer assertStopped(c, pb)
   217  
   218  	slot := int64(1497960150)
   219  	uuid := "test-uuid"
   220  	c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil)
   221  	c.Assert(pb.Ping("test-uuid", slot, "0", 16), jc.ErrorIsNil)
   222  	docId := fmt.Sprintf("%s:%d", uuid, slot)
   223  	var res bson.M
   224  	// We should not have found it yet
   225  	c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound)
   226  	// We will wait up to 1s for the write to succeed. While waiting, we will ping on another slot, to
   227  	// hint at other pingers that are still active, without messing up our final assertion.
   228  	slot2 := slot + 30
   229  	for i := 0; i < 1000; i++ {
   230  		time.Sleep(time.Millisecond)
   231  		err := s.pings.FindId(docId).One(&res)
   232  		slot2 = slot2 + 30
   233  		pb.Ping("test-uuid", slot2, "0", 1)
   234  		waitTime := time.Since(t)
   235  		if waitTime < time.Duration(35*time.Millisecond) {
   236  			// Officially it should take a minimum of 50*0.8 = 40ms.
   237  			// make sure the timer hasn't flushed yet
   238  			c.Assert(err, gc.Equals, mgo.ErrNotFound,
   239  				gc.Commentf("PingBatcher flushed too soon, expected at least 15ms"))
   240  			continue
   241  		}
   242  		if err == nil {
   243  			c.Logf("found the document after %v", waitTime)
   244  			break
   245  		} else {
   246  			c.Logf("no document after %v", waitTime)
   247  			c.Assert(err, gc.Equals, mgo.ErrNotFound)
   248  		}
   249  	}
   250  	// If it wasn't found, this check will fail
   251  	c.Check(res, gc.DeepEquals, bson.M{
   252  		"_id":  docId,
   253  		"slot": slot,
   254  		"alive": bson.M{
   255  			"0": int64(24),
   256  		},
   257  	})
   258  }
   259  
   260  func (s *PingBatcherSuite) TestPingBatcherFlushesOnShutdown(c *gc.C) {
   261  	// Make sure it doesn't flush on a timer
   262  	pb := presence.NewPingBatcher(s.presence, time.Hour)
   263  	defer assertStopped(c, pb)
   264  	slot := int64(1497960150)
   265  	uuid := "test-uuid"
   266  	docId := fmt.Sprintf("%s:%d", uuid, slot)
   267  	var res bson.M
   268  	// We should not have found it yet
   269  	c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound)
   270  	c.Assert(pb.Ping("test-uuid", slot, "0", 8), jc.ErrorIsNil)
   271  	// Still not found, as it hasn't been flushed
   272  	c.Assert(s.pings.FindId(docId).One(&res), gc.Equals, mgo.ErrNotFound)
   273  	c.Assert(pb.Stop(), jc.ErrorIsNil)
   274  	// And now we find it
   275  	c.Assert(s.pings.FindId(docId).One(&res), jc.ErrorIsNil)
   276  	c.Check(res, gc.DeepEquals, bson.M{
   277  		"_id":  docId,
   278  		"slot": slot,
   279  		"alive": bson.M{
   280  			"0": int64(8),
   281  		},
   282  	})
   283  }
   284  
   285  func (s *PingBatcherSuite) TestStoppedPingerRejectsPings(c *gc.C) {
   286  	pb := presence.NewPingBatcher(s.presence, testing.ShortWait)
   287  	defer assertStopped(c, pb)
   288  	c.Assert(pb.Stop(), jc.ErrorIsNil)
   289  	slot := int64(1497960150)
   290  	uuid := "test-uuid"
   291  	err := pb.Ping(uuid, slot, "0", 8)
   292  	c.Assert(err, gc.ErrorMatches, "PingBatcher is stopped")
   293  	err = pb.Sync()
   294  	c.Assert(err, gc.ErrorMatches, "PingBatcher is stopped")
   295  }
   296  
   297  func (s *PingBatcherSuite) TestNewDeadPingBatcher(c *gc.C) {
   298  	testErr := fmt.Errorf("this is an error")
   299  	pb := presence.NewDeadPingBatcher(testErr)
   300  	slot := int64(1497960150)
   301  	uuid := "test-uuid"
   302  	err := pb.Ping(uuid, slot, "0", 8)
   303  	c.Assert(err, gc.ErrorMatches, "this is an error")
   304  
   305  	err = pb.Stop()
   306  	c.Assert(err, gc.ErrorMatches, "this is an error")
   307  }