github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/raftlease/store_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftlease_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/pubsub"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/core/globalclock"
    19  	"github.com/juju/juju/core/lease"
    20  	"github.com/juju/juju/core/raftlease"
    21  	coretesting "github.com/juju/juju/testing"
    22  )
    23  
    24  type storeSuite struct {
    25  	testing.IsolationSuite
    26  
    27  	clock *testclock.Clock
    28  	fsm   *fakeFSM
    29  	hub   *pubsub.StructuredHub
    30  	store *raftlease.Store
    31  }
    32  
    33  var _ = gc.Suite(&storeSuite{})
    34  
    35  func (s *storeSuite) SetUpTest(c *gc.C) {
    36  	s.IsolationSuite.SetUpTest(c)
    37  
    38  	startTime, err := time.Parse(time.RFC3339, "2018-08-08T08:08:08+08:00")
    39  	c.Assert(err, jc.ErrorIsNil)
    40  	s.clock = testclock.NewClock(startTime)
    41  	s.fsm = &fakeFSM{
    42  		leases:     make(map[lease.Key]lease.Info),
    43  		globalTime: s.clock.Now(),
    44  	}
    45  	s.hub = pubsub.NewStructuredHub(nil)
    46  	s.store = raftlease.NewStore(raftlease.StoreConfig{
    47  		FSM:          s.fsm,
    48  		Hub:          s.hub,
    49  		Trapdoor:     FakeTrapdoor,
    50  		RequestTopic: "lease.request",
    51  		ResponseTopic: func(reqID uint64) string {
    52  			return fmt.Sprintf("lease.request.%d", reqID)
    53  		},
    54  		Clock:          s.clock,
    55  		ForwardTimeout: time.Second,
    56  	})
    57  }
    58  
    59  func (s *storeSuite) TestClaim(c *gc.C) {
    60  	s.handleHubRequest(c,
    61  		func() {
    62  			err := s.store.ClaimLease(
    63  				lease.Key{"warframe", "rhino", "prime"},
    64  				lease.Request{"lotus", time.Second},
    65  			)
    66  			c.Assert(err, jc.ErrorIsNil)
    67  		},
    68  
    69  		raftlease.Command{
    70  			Version:   1,
    71  			Operation: raftlease.OperationClaim,
    72  			Namespace: "warframe",
    73  			ModelUUID: "rhino",
    74  			Lease:     "prime",
    75  			Holder:    "lotus",
    76  			Duration:  time.Second,
    77  		},
    78  		func(req raftlease.ForwardRequest) {
    79  			_, err := s.hub.Publish(
    80  				req.ResponseTopic,
    81  				raftlease.ForwardResponse{},
    82  			)
    83  			c.Check(err, jc.ErrorIsNil)
    84  		},
    85  	)
    86  }
    87  
    88  func (s *storeSuite) TestClaimTimeout(c *gc.C) {
    89  	s.handleHubRequest(c,
    90  		func() {
    91  			errChan := make(chan error)
    92  			go func() {
    93  				errChan <- s.store.ClaimLease(
    94  					lease.Key{"warframe", "vauban", "prime"},
    95  					lease.Request{"vor", time.Second},
    96  				)
    97  			}()
    98  			// Jump time forward further than the 1-second forward
    99  			// timeout.
   100  			c.Assert(s.clock.WaitAdvance(2*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil)
   101  
   102  			select {
   103  			case err := <-errChan:
   104  				c.Assert(err, jc.Satisfies, lease.IsTimeout)
   105  			case <-time.After(coretesting.LongWait):
   106  				c.Fatalf("timed out waiting for claim error")
   107  			}
   108  		},
   109  
   110  		raftlease.Command{
   111  			Version:   1,
   112  			Operation: raftlease.OperationClaim,
   113  			Namespace: "warframe",
   114  			ModelUUID: "vauban",
   115  			Lease:     "prime",
   116  			Holder:    "vor",
   117  			Duration:  time.Second,
   118  		},
   119  		func(req raftlease.ForwardRequest) {
   120  			// We never send a response, to trigger a timeout.
   121  		},
   122  	)
   123  }
   124  
   125  func (s *storeSuite) TestClaimInvalid(c *gc.C) {
   126  	s.handleHubRequest(c,
   127  		func() {
   128  			err := s.store.ClaimLease(
   129  				lease.Key{"warframe", "volt", "umbra"},
   130  				lease.Request{"maroo", 3 * time.Second},
   131  			)
   132  			c.Assert(err, jc.Satisfies, lease.IsInvalid)
   133  		},
   134  
   135  		raftlease.Command{
   136  			Version:   1,
   137  			Operation: raftlease.OperationClaim,
   138  			Namespace: "warframe",
   139  			ModelUUID: "volt",
   140  			Lease:     "umbra",
   141  			Holder:    "maroo",
   142  			Duration:  3 * time.Second,
   143  		},
   144  		func(req raftlease.ForwardRequest) {
   145  			_, err := s.hub.Publish(
   146  				req.ResponseTopic,
   147  				raftlease.ForwardResponse{
   148  					Error: &raftlease.ResponseError{
   149  						Code: "invalid",
   150  					},
   151  				},
   152  			)
   153  			c.Check(err, jc.ErrorIsNil)
   154  		},
   155  	)
   156  }
   157  
   158  func (s *storeSuite) TestExtend(c *gc.C) {
   159  	s.handleHubRequest(c,
   160  		func() {
   161  			err := s.store.ExtendLease(
   162  				lease.Key{"warframe", "frost", "prime"},
   163  				lease.Request{"konzu", time.Second},
   164  			)
   165  			c.Assert(err, jc.ErrorIsNil)
   166  		},
   167  
   168  		raftlease.Command{
   169  			Version:   1,
   170  			Operation: raftlease.OperationExtend,
   171  			Namespace: "warframe",
   172  			ModelUUID: "frost",
   173  			Lease:     "prime",
   174  			Holder:    "konzu",
   175  			Duration:  time.Second,
   176  		},
   177  		func(req raftlease.ForwardRequest) {
   178  			_, err := s.hub.Publish(
   179  				req.ResponseTopic,
   180  				raftlease.ForwardResponse{},
   181  			)
   182  			c.Check(err, jc.ErrorIsNil)
   183  		},
   184  	)
   185  }
   186  
   187  func (s *storeSuite) TestExpire(c *gc.C) {
   188  	err := s.store.ExpireLease(
   189  		lease.Key{"warframe", "oberon", "prime"},
   190  	)
   191  	c.Assert(err, jc.Satisfies, lease.IsInvalid)
   192  }
   193  
   194  func (s *storeSuite) TestLeases(c *gc.C) {
   195  	in5Seconds := s.clock.Now().Add(5 * time.Second)
   196  	in10Seconds := s.clock.Now().Add(10 * time.Second)
   197  	lease1 := lease.Key{"quam", "olim", "abrahe"}
   198  	lease2 := lease.Key{"la", "cry", "mosa"}
   199  	s.fsm.leases[lease1] = lease.Info{
   200  		Holder: "verdi",
   201  		Expiry: in10Seconds,
   202  	}
   203  	s.fsm.leases[lease2] = lease.Info{
   204  		Holder: "mozart",
   205  		Expiry: in5Seconds,
   206  	}
   207  	result := s.store.Leases()
   208  	c.Assert(len(result), gc.Equals, 2)
   209  
   210  	r1 := result[lease1]
   211  	c.Assert(r1.Holder, gc.Equals, "verdi")
   212  	c.Assert(r1.Expiry, gc.Equals, in10Seconds)
   213  
   214  	// Can't compare trapdoors directly.
   215  	var out string
   216  	err := r1.Trapdoor(0, &out)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	c.Assert(out, gc.Equals, "{quam olim abrahe} held by verdi")
   219  
   220  	r2 := result[lease2]
   221  	c.Assert(r2.Holder, gc.Equals, "mozart")
   222  	c.Assert(r2.Expiry, gc.Equals, in5Seconds)
   223  
   224  	err = r2.Trapdoor(0, &out)
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	c.Assert(out, gc.Equals, "{la cry mosa} held by mozart")
   227  }
   228  
   229  func (s *storeSuite) TestLeasesFilter(c *gc.C) {
   230  	lease1 := lease.Key{Namespace: "quam", ModelUUID: "olim", Lease: "abrahe"}
   231  	lease2 := lease.Key{Namespace: "la", ModelUUID: "cry", Lease: "mosa"}
   232  
   233  	_ = s.store.Leases(lease1, lease2)
   234  	s.fsm.CheckCallNames(c, "Leases")
   235  	c.Check(s.fsm.Calls()[0].Args[1], jc.SameContents, []lease.Key{lease1, lease2})
   236  }
   237  
   238  func (s *storeSuite) TestPin(c *gc.C) {
   239  	machine := names.NewMachineTag("0").String()
   240  	s.handleHubRequest(c,
   241  		func() {
   242  			err := s.store.PinLease(
   243  				lease.Key{"warframe", "frost", "prime"},
   244  				machine,
   245  			)
   246  			c.Assert(err, jc.ErrorIsNil)
   247  		},
   248  		raftlease.Command{
   249  			Version:   1,
   250  			Operation: raftlease.OperationPin,
   251  			Namespace: "warframe",
   252  			ModelUUID: "frost",
   253  			Lease:     "prime",
   254  			PinEntity: machine,
   255  		},
   256  		func(req raftlease.ForwardRequest) {
   257  			_, err := s.hub.Publish(
   258  				req.ResponseTopic,
   259  				raftlease.ForwardResponse{},
   260  			)
   261  			c.Check(err, jc.ErrorIsNil)
   262  		},
   263  	)
   264  }
   265  
   266  func (s *storeSuite) TestUnpin(c *gc.C) {
   267  	machine := names.NewMachineTag("0").String()
   268  	s.handleHubRequest(c,
   269  		func() {
   270  			err := s.store.UnpinLease(
   271  				lease.Key{"warframe", "frost", "prime"},
   272  				machine,
   273  			)
   274  			c.Assert(err, jc.ErrorIsNil)
   275  		},
   276  		raftlease.Command{
   277  			Version:   1,
   278  			Operation: raftlease.OperationUnpin,
   279  			Namespace: "warframe",
   280  			ModelUUID: "frost",
   281  			Lease:     "prime",
   282  			PinEntity: machine,
   283  		},
   284  		func(req raftlease.ForwardRequest) {
   285  			_, err := s.hub.Publish(
   286  				req.ResponseTopic,
   287  				raftlease.ForwardResponse{},
   288  			)
   289  			c.Check(err, jc.ErrorIsNil)
   290  		},
   291  	)
   292  }
   293  
   294  func (s *storeSuite) TestPinned(c *gc.C) {
   295  	s.fsm.pinned = map[lease.Key][]string{}
   296  	c.Check(s.store.Pinned(), gc.DeepEquals, s.fsm.pinned)
   297  	s.fsm.CheckCallNames(c, "Pinned")
   298  }
   299  
   300  // handleHubRequest takes the action that triggers the request, the
   301  // expected command, and a function that will be run to make checks on
   302  // the request and send the response back.
   303  func (s *storeSuite) handleHubRequest(
   304  	c *gc.C,
   305  	action func(),
   306  	expectCommand raftlease.Command,
   307  	responder func(raftlease.ForwardRequest),
   308  ) {
   309  	expected := marshal(c, expectCommand)
   310  	called := make(chan struct{})
   311  	unsubscribe, err := s.hub.Subscribe(
   312  		"lease.request",
   313  		func(_ string, req raftlease.ForwardRequest, err error) {
   314  			defer close(called)
   315  			c.Check(err, jc.ErrorIsNil)
   316  			c.Check(req.Command, gc.DeepEquals, expected)
   317  			responder(req)
   318  		},
   319  	)
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	defer unsubscribe()
   322  
   323  	action()
   324  	select {
   325  	case <-called:
   326  	case <-time.After(coretesting.LongWait):
   327  		c.Fatalf("timed out waiting for hub message")
   328  	}
   329  }
   330  
   331  func (s *storeSuite) TestAdvance(c *gc.C) {
   332  	fromTime := s.clock.Now()
   333  
   334  	s.handleHubRequest(c,
   335  		func() {
   336  			err := s.store.Advance(10 * time.Second)
   337  			c.Assert(err, jc.ErrorIsNil)
   338  		},
   339  		raftlease.Command{
   340  			Version:   1,
   341  			Operation: raftlease.OperationSetTime,
   342  			OldTime:   fromTime,
   343  			NewTime:   fromTime.Add(10 * time.Second),
   344  		},
   345  		func(req raftlease.ForwardRequest) {
   346  			c.Check(req.ResponseTopic, gc.Equals, "lease.request.1")
   347  			_, err := s.hub.Publish(
   348  				req.ResponseTopic,
   349  				raftlease.ForwardResponse{},
   350  			)
   351  			c.Check(err, jc.ErrorIsNil)
   352  		},
   353  	)
   354  	// The store time advances, as seen in the next update.
   355  	s.handleHubRequest(c,
   356  		func() {
   357  			err := s.store.Advance(5 * time.Second)
   358  			c.Assert(err, jc.ErrorIsNil)
   359  		},
   360  		raftlease.Command{
   361  			Version:   1,
   362  			Operation: raftlease.OperationSetTime,
   363  			OldTime:   fromTime.Add(10 * time.Second),
   364  			NewTime:   fromTime.Add(15 * time.Second),
   365  		},
   366  		func(req raftlease.ForwardRequest) {
   367  			c.Check(req.ResponseTopic, gc.Equals, "lease.request.2")
   368  			_, err := s.hub.Publish(
   369  				req.ResponseTopic,
   370  				raftlease.ForwardResponse{},
   371  			)
   372  			c.Check(err, jc.ErrorIsNil)
   373  		},
   374  	)
   375  }
   376  
   377  func (s *storeSuite) TestAdvanceConcurrentUpdate(c *gc.C) {
   378  	fromTime := s.clock.Now()
   379  	plus5Sec := fromTime.Add(5 * time.Second)
   380  	plus10Sec := fromTime.Add(10 * time.Second)
   381  	s.fsm.globalTime = plus5Sec
   382  
   383  	s.handleHubRequest(c,
   384  		func() {
   385  			err := s.store.Advance(10 * time.Second)
   386  			c.Assert(err, jc.Satisfies, globalclock.IsConcurrentUpdate)
   387  		},
   388  		raftlease.Command{
   389  			Version:   1,
   390  			Operation: raftlease.OperationSetTime,
   391  			OldTime:   fromTime,
   392  			NewTime:   plus10Sec,
   393  		},
   394  		func(req raftlease.ForwardRequest) {
   395  			_, err := s.hub.Publish(
   396  				req.ResponseTopic,
   397  				raftlease.ForwardResponse{
   398  					Error: &raftlease.ResponseError{
   399  						Code: "concurrent-update",
   400  					},
   401  				},
   402  			)
   403  			c.Check(err, jc.ErrorIsNil)
   404  		},
   405  	)
   406  
   407  	// Check that the store updates time from the FSM for when we try
   408  	// again.
   409  	s.handleHubRequest(c,
   410  		func() {
   411  			err := s.store.Advance(10 * time.Second)
   412  			c.Assert(err, jc.ErrorIsNil)
   413  		},
   414  		raftlease.Command{
   415  			Version:   1,
   416  			Operation: raftlease.OperationSetTime,
   417  			OldTime:   plus5Sec,
   418  			NewTime:   fromTime.Add(15 * time.Second),
   419  		},
   420  		func(req raftlease.ForwardRequest) {
   421  			c.Check(req.ResponseTopic, gc.Equals, "lease.request.2")
   422  			_, err := s.hub.Publish(
   423  				req.ResponseTopic,
   424  				raftlease.ForwardResponse{},
   425  			)
   426  			c.Check(err, jc.ErrorIsNil)
   427  		},
   428  	)
   429  }
   430  
   431  func (s *storeSuite) TestAdvanceTimeout(c *gc.C) {
   432  	fromTime := s.clock.Now()
   433  	s.handleHubRequest(c,
   434  		func() {
   435  			errChan := make(chan error)
   436  			go func() {
   437  				errChan <- s.store.Advance(10 * time.Second)
   438  			}()
   439  
   440  			// Move time forward to trigger the timeout.
   441  			c.Assert(s.clock.WaitAdvance(2*time.Second, coretesting.LongWait, 1), jc.ErrorIsNil)
   442  
   443  			select {
   444  			case err := <-errChan:
   445  				c.Assert(err, jc.Satisfies, globalclock.IsTimeout)
   446  			case <-time.After(coretesting.LongWait):
   447  				c.Fatalf("timed out waiting for advance error")
   448  			}
   449  		},
   450  		raftlease.Command{
   451  			Version:   1,
   452  			Operation: raftlease.OperationSetTime,
   453  			OldTime:   fromTime,
   454  			NewTime:   fromTime.Add(10 * time.Second),
   455  		},
   456  		func(raftlease.ForwardRequest) {
   457  			// No response sent, to trigger the timeout.
   458  		})
   459  }
   460  
   461  func (s *storeSuite) TestAsResponseError(c *gc.C) {
   462  	c.Assert(
   463  		raftlease.AsResponseError(lease.ErrInvalid),
   464  		gc.DeepEquals,
   465  		&raftlease.ResponseError{
   466  			"invalid lease operation",
   467  			"invalid",
   468  		},
   469  	)
   470  	c.Assert(
   471  		raftlease.AsResponseError(globalclock.ErrConcurrentUpdate),
   472  		gc.DeepEquals,
   473  		&raftlease.ResponseError{
   474  			"clock was updated concurrently, retry",
   475  			"concurrent-update",
   476  		},
   477  	)
   478  	c.Assert(
   479  		raftlease.AsResponseError(errors.Errorf("generic")),
   480  		gc.DeepEquals,
   481  		&raftlease.ResponseError{
   482  			"generic",
   483  			"error",
   484  		},
   485  	)
   486  }
   487  
   488  func (s *storeSuite) TestRecoverError(c *gc.C) {
   489  	c.Assert(raftlease.RecoverError(nil), gc.Equals, nil)
   490  	re := func(msg, code string) error {
   491  		return raftlease.RecoverError(&raftlease.ResponseError{
   492  			Message: msg,
   493  			Code:    code,
   494  		})
   495  	}
   496  	c.Assert(re("", "invalid"), jc.Satisfies, lease.IsInvalid)
   497  	c.Assert(re("", "concurrent-update"), jc.Satisfies, globalclock.IsConcurrentUpdate)
   498  	c.Assert(re("something", "else"), gc.ErrorMatches, "something")
   499  }
   500  
   501  type fakeFSM struct {
   502  	testing.Stub
   503  	leases     map[lease.Key]lease.Info
   504  	globalTime time.Time
   505  	pinned     map[lease.Key][]string
   506  }
   507  
   508  func (f *fakeFSM) Leases(t func() time.Time, keys ...lease.Key) map[lease.Key]lease.Info {
   509  	f.AddCall("Leases", t(), keys)
   510  	return f.leases
   511  }
   512  
   513  func (f *fakeFSM) Pinned() map[lease.Key][]string {
   514  	f.AddCall("Pinned")
   515  	return f.pinned
   516  }
   517  
   518  func (f *fakeFSM) GlobalTime() time.Time {
   519  	return f.globalTime
   520  }
   521  
   522  func FakeTrapdoor(key lease.Key, holder string) lease.Trapdoor {
   523  	return func(attempt int, out interface{}) error {
   524  		if s, ok := out.(*string); ok {
   525  			*s = fmt.Sprintf("%v held by %s", key, holder)
   526  			return nil
   527  		}
   528  		return errors.Errorf("bad input")
   529  	}
   530  }
   531  
   532  func marshal(c *gc.C, command raftlease.Command) string {
   533  	result, err := command.Marshal()
   534  	c.Assert(err, jc.ErrorIsNil)
   535  	return string(result)
   536  }