github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lease/manager_claim_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/mattn/go-sqlite3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	corelease "github.com/juju/juju/core/lease"
    18  	"github.com/juju/juju/worker/lease"
    19  )
    20  
    21  type ClaimSuite struct {
    22  	testing.IsolationSuite
    23  }
    24  
    25  var _ = gc.Suite(&ClaimSuite{})
    26  
    27  func (s *ClaimSuite) TestClaimLease_Success(c *gc.C) {
    28  	fix := &Fixture{
    29  		expectCalls: []call{{
    30  			method: "ClaimLease",
    31  			args: []interface{}{
    32  				corelease.Key{
    33  					Namespace: "namespace",
    34  					ModelUUID: "modelUUID",
    35  					Lease:     "redis",
    36  				},
    37  				corelease.Request{"redis/0", time.Minute},
    38  			},
    39  		}},
    40  	}
    41  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
    42  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
    43  		c.Check(err, jc.ErrorIsNil)
    44  	})
    45  }
    46  
    47  func (s *ClaimSuite) TestClaimLease_Success_SameHolder(c *gc.C) {
    48  	fix := &Fixture{
    49  		expectCalls: []call{{
    50  			method: "ClaimLease",
    51  			args: []interface{}{
    52  				corelease.Key{
    53  					Namespace: "namespace",
    54  					ModelUUID: "modelUUID",
    55  					Lease:     "redis",
    56  				},
    57  				corelease.Request{"redis/0", time.Minute},
    58  			},
    59  			err: corelease.ErrInvalid,
    60  			callback: func(leases map[corelease.Key]corelease.Info) {
    61  				leases[key("redis")] = corelease.Info{
    62  					Holder: "redis/0",
    63  					Expiry: offset(time.Second),
    64  				}
    65  			},
    66  		}, {
    67  			method: "ExtendLease",
    68  			args: []interface{}{
    69  				corelease.Key{
    70  					Namespace: "namespace",
    71  					ModelUUID: "modelUUID",
    72  					Lease:     "redis",
    73  				},
    74  				corelease.Request{"redis/0", time.Minute},
    75  			},
    76  		}},
    77  	}
    78  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
    79  		// On the first attempt, we don't see ourselves in the leases, so we try
    80  		// to Claim the lease. But Primary thinks we already have the lease, so it
    81  		// refuses. After claiming, we wait 50ms to let the refresh happen, then
    82  		// we notice that we are the holder, so we Extend instead of Claim.
    83  		var wg sync.WaitGroup
    84  		wg.Add(1)
    85  		go func() {
    86  			err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
    87  			c.Check(err, jc.ErrorIsNil)
    88  			wg.Done()
    89  		}()
    90  		c.Check(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 2), jc.ErrorIsNil)
    91  		wg.Wait()
    92  	})
    93  }
    94  
    95  func (s *ClaimSuite) TestClaimLeaseFailureHeldByClaimer(c *gc.C) {
    96  	fix := &Fixture{
    97  		expectCalls: []call{{
    98  			method: "ClaimLease",
    99  			args: []interface{}{
   100  				corelease.Key{
   101  					Namespace: "namespace",
   102  					ModelUUID: "modelUUID",
   103  					Lease:     "redis",
   104  				},
   105  				corelease.Request{Holder: "redis/0", Duration: time.Minute},
   106  			},
   107  			err: corelease.ErrInvalid,
   108  			callback: func(leases map[corelease.Key]corelease.Info) {
   109  				leases[key("redis")] = corelease.Info{
   110  					Holder: "redis/1",
   111  					Expiry: offset(time.Second),
   112  				}
   113  			},
   114  		}},
   115  	}
   116  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   117  		// When the Claim starts, it will first get a LeaseInvalid, it will then
   118  		// wait 50ms before trying again, since it is clear that our Leases map
   119  		// does not have the most up-to-date information. We then wake up again
   120  		// and see that our leases have expired and thus let things go.
   121  		var wg sync.WaitGroup
   122  		wg.Add(1)
   123  		go func() {
   124  			err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   125  			c.Check(err, gc.Equals, corelease.ErrClaimDenied)
   126  			wg.Done()
   127  		}()
   128  		c.Check(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 2), jc.ErrorIsNil)
   129  		wg.Wait()
   130  	})
   131  }
   132  
   133  func (s *ClaimSuite) TestClaimLeaseFailureHeldByOther(c *gc.C) {
   134  	fix := &Fixture{
   135  		expectCalls: []call{{
   136  			method: "ClaimLease",
   137  			args: []interface{}{
   138  				corelease.Key{
   139  					Namespace: "namespace",
   140  					ModelUUID: "modelUUID",
   141  					Lease:     "redis",
   142  				},
   143  				corelease.Request{Holder: "redis/0", Duration: time.Minute},
   144  			},
   145  			err: corelease.ErrHeld,
   146  		}},
   147  	}
   148  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   149  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   150  		c.Check(err, gc.Equals, corelease.ErrClaimDenied)
   151  	})
   152  }
   153  
   154  func (s *ClaimSuite) TestClaimLease_Failure_Error(c *gc.C) {
   155  	fix := &Fixture{
   156  		expectCalls: []call{{
   157  			method: "ClaimLease",
   158  			args: []interface{}{
   159  				corelease.Key{
   160  					Namespace: "namespace",
   161  					ModelUUID: "modelUUID",
   162  					Lease:     "redis",
   163  				},
   164  				corelease.Request{"redis/0", time.Minute},
   165  			},
   166  			err: errors.New("lol borken"),
   167  		}},
   168  		expectDirty: true,
   169  	}
   170  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   171  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   172  		c.Check(err, gc.ErrorMatches, "lease manager stopped")
   173  		err = manager.Wait()
   174  		c.Check(err, gc.ErrorMatches, "lol borken")
   175  	})
   176  }
   177  
   178  func (s *ClaimSuite) TestExtendLease_Success(c *gc.C) {
   179  	fix := &Fixture{
   180  		leases: map[corelease.Key]corelease.Info{
   181  			key("redis"): {
   182  				Holder: "redis/0",
   183  				Expiry: offset(time.Second),
   184  			},
   185  		},
   186  		expectCalls: []call{{
   187  			method: "ExtendLease",
   188  			args: []interface{}{
   189  				corelease.Key{
   190  					Namespace: "namespace",
   191  					ModelUUID: "modelUUID",
   192  					Lease:     "redis",
   193  				},
   194  				corelease.Request{"redis/0", time.Minute},
   195  			},
   196  		}},
   197  	}
   198  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   199  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   200  		c.Check(err, jc.ErrorIsNil)
   201  	})
   202  }
   203  
   204  func (s *ClaimSuite) TestExtendLease_Success_Expired(c *gc.C) {
   205  	fix := &Fixture{
   206  		leases: map[corelease.Key]corelease.Info{
   207  			key("redis"): {
   208  				Holder: "redis/0",
   209  				Expiry: offset(time.Second),
   210  			},
   211  		},
   212  		expectCalls: []call{{
   213  			method: "ExtendLease",
   214  			args: []interface{}{
   215  				corelease.Key{
   216  					Namespace: "namespace",
   217  					ModelUUID: "modelUUID",
   218  					Lease:     "redis",
   219  				},
   220  				corelease.Request{"redis/0", time.Minute},
   221  			},
   222  			err: corelease.ErrInvalid,
   223  			callback: func(leases map[corelease.Key]corelease.Info) {
   224  				delete(leases, key("redis"))
   225  			},
   226  		}, {
   227  			method: "ClaimLease",
   228  			args: []interface{}{
   229  				corelease.Key{
   230  					Namespace: "namespace",
   231  					ModelUUID: "modelUUID",
   232  					Lease:     "redis",
   233  				},
   234  				corelease.Request{"redis/0", time.Minute},
   235  			},
   236  		}},
   237  	}
   238  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   239  		// On the first attempt, we think we are the holder, but Primary says "nope".
   240  		// So we wait 50ms for the Leases to get updated. At which point, we have
   241  		// reloaded our Leases and see that *nobody* is the holder. So then we try
   242  		// again and successfully Claim the lease.
   243  		var wg sync.WaitGroup
   244  		wg.Add(1)
   245  		go func() {
   246  			err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   247  			c.Check(err, jc.ErrorIsNil)
   248  			wg.Done()
   249  		}()
   250  		c.Check(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 2), jc.ErrorIsNil)
   251  		wg.Wait()
   252  	})
   253  }
   254  
   255  func (s *ClaimSuite) TestExtendLease_Failure_OtherHolder(c *gc.C) {
   256  	fix := &Fixture{
   257  		leases: map[corelease.Key]corelease.Info{
   258  			key("redis"): {
   259  				Holder: "redis/0",
   260  				Expiry: offset(time.Second),
   261  			},
   262  		},
   263  		expectCalls: []call{{
   264  			method: "ExtendLease",
   265  			args: []interface{}{
   266  				corelease.Key{
   267  					Namespace: "namespace",
   268  					ModelUUID: "modelUUID",
   269  					Lease:     "redis",
   270  				},
   271  				corelease.Request{"redis/0", time.Minute},
   272  			},
   273  			err: corelease.ErrInvalid,
   274  			callback: func(leases map[corelease.Key]corelease.Info) {
   275  				leases[key("redis")] = corelease.Info{
   276  					Holder: "redis/1",
   277  					Expiry: offset(time.Second),
   278  				}
   279  			},
   280  		}},
   281  	}
   282  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   283  		// When the Claim starts, it will first get a LeaseInvalid, it will then
   284  		// wait 50ms before trying again, since it is clear that our Leases map
   285  		// does not have the most up-to-date information. We then wake up again
   286  		// and see that our leases have expired and thus let things go.
   287  		var wg sync.WaitGroup
   288  		wg.Add(1)
   289  		go func() {
   290  			err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   291  			c.Check(err, gc.Equals, corelease.ErrClaimDenied)
   292  			wg.Done()
   293  		}()
   294  		c.Check(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 2), jc.ErrorIsNil)
   295  		wg.Wait()
   296  	})
   297  }
   298  
   299  func (s *ClaimSuite) TestExtendLease_Failure_Retryable(c *gc.C) {
   300  	fix := &Fixture{
   301  		leases: map[corelease.Key]corelease.Info{
   302  			key("redis"): {
   303  				Holder: "redis/0",
   304  				Expiry: offset(time.Second),
   305  			},
   306  		},
   307  		expectCalls: []call{{
   308  			method: "ExtendLease",
   309  			args: []interface{}{
   310  				corelease.Key{
   311  					Namespace: "namespace",
   312  					ModelUUID: "modelUUID",
   313  					Lease:     "redis",
   314  				},
   315  				corelease.Request{Holder: "redis/0", Duration: time.Minute},
   316  			},
   317  			err: sqlite3.ErrLocked,
   318  			callback: func(leases map[corelease.Key]corelease.Info) {
   319  				leases[key("redis")] = corelease.Info{
   320  					Holder: "redis/1",
   321  					Expiry: offset(time.Second),
   322  				}
   323  			},
   324  		}},
   325  	}
   326  	fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) {
   327  		// When the Claim starts, it will first get a LeaseInvalid, it will then
   328  		// wait 50ms before trying again, since it is clear that our Leases map
   329  		// does not have the most up-to-date information. We then wake up again
   330  		// and see that our leases have expired and thus let things go.
   331  		var wg sync.WaitGroup
   332  		wg.Add(1)
   333  		go func() {
   334  			err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   335  			c.Check(err, gc.Equals, corelease.ErrClaimDenied)
   336  			wg.Done()
   337  		}()
   338  		c.Check(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 2), jc.ErrorIsNil)
   339  		wg.Wait()
   340  	})
   341  }
   342  
   343  func (s *ClaimSuite) TestExtendLease_Failure_Error(c *gc.C) {
   344  	fix := &Fixture{
   345  		leases: map[corelease.Key]corelease.Info{
   346  			key("redis"): {
   347  				Holder: "redis/0",
   348  				Expiry: offset(time.Second),
   349  			},
   350  		},
   351  		expectCalls: []call{{
   352  			method: "ExtendLease",
   353  			args: []interface{}{
   354  				key("redis"),
   355  				corelease.Request{"redis/0", time.Minute},
   356  			},
   357  			err: errors.New("boom splat"),
   358  		}},
   359  		expectDirty: true,
   360  	}
   361  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   362  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   363  		c.Check(err, gc.ErrorMatches, "lease manager stopped")
   364  		err = manager.Wait()
   365  		c.Check(err, gc.ErrorMatches, "boom splat")
   366  	})
   367  }
   368  
   369  func (s *ClaimSuite) TestOtherHolder_Failure(c *gc.C) {
   370  	fix := &Fixture{
   371  		leases: map[corelease.Key]corelease.Info{
   372  			key("redis"): {
   373  				Holder: "redis/1",
   374  				Expiry: offset(time.Second),
   375  			},
   376  		},
   377  	}
   378  	fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) {
   379  		err := getClaimer(c, manager).Claim("redis", "redis/0", time.Minute)
   380  		c.Check(err, gc.Equals, corelease.ErrClaimDenied)
   381  	})
   382  }
   383  
   384  func getClaimer(c *gc.C, manager *lease.Manager) corelease.Claimer {
   385  	claimer, err := manager.Claimer("namespace", "modelUUID")
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	return claimer
   388  }