github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/machinelock/machinelock_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machinelock_test
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/mutex/v2"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/core/machinelock"
    19  	"github.com/juju/juju/core/paths"
    20  	jujutesting "github.com/juju/juju/testing"
    21  )
    22  
    23  type Lock interface {
    24  	Acquire(machinelock.Spec) (func(), error)
    25  	Report(...machinelock.ReportOption) (string, error)
    26  }
    27  
    28  type lockSuite struct {
    29  	testing.IsolationSuite
    30  	logfile string
    31  	clock   *fakeClock
    32  	lock    Lock
    33  
    34  	notify       chan string
    35  	allowAcquire chan struct{}
    36  	release      chan struct{}
    37  }
    38  
    39  var _ = gc.Suite(&lockSuite{})
    40  
    41  func (s *lockSuite) SetUpTest(c *gc.C) {
    42  	s.IsolationSuite.SetUpTest(c)
    43  	s.clock = &fakeClock{time.Date(2018, 7, 10, 12, 0, 0, 0, time.UTC)}
    44  
    45  	s.logfile = filepath.Join(c.MkDir(), "logfile")
    46  
    47  	s.notify = make(chan string)
    48  	s.allowAcquire = make(chan struct{})
    49  	s.release = make(chan struct{})
    50  
    51  	lock, err := machinelock.NewTestLock(machinelock.Config{
    52  		AgentName:   "test",
    53  		Clock:       s.clock,
    54  		Logger:      loggo.GetLogger("test"),
    55  		LogFilename: s.logfile,
    56  	}, s.acquireLock)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.lock = lock
    59  
    60  	s.AddCleanup(func(c *gc.C) {
    61  		// release all the pending goroutines
    62  		close(s.allowAcquire)
    63  	})
    64  }
    65  
    66  func (s *lockSuite) TestLogFilePermissions(c *gc.C) {
    67  	info, err := os.Stat(s.logfile)
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	c.Assert(info.Mode(), gc.Equals, paths.LogfilePermission)
    70  }
    71  
    72  func (s *lockSuite) TestEmptyOutput(c *gc.C) {
    73  	output, err := s.lock.Report()
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(output, gc.Equals, `
    76  test:
    77    holder: none
    78  `[1:])
    79  
    80  	output, err = s.lock.Report(machinelock.ShowDetailsYAML)
    81  	c.Assert(err, jc.ErrorIsNil)
    82  	c.Assert(output, gc.Equals, `
    83  test:
    84    holder: null
    85  `[1:])
    86  }
    87  
    88  func (s *lockSuite) TestWaitingOutput(c *gc.C) {
    89  	s.addWaiting(c, "worker1", "being busy")
    90  	s.clock.Advance(time.Minute)
    91  	s.addWaiting(c, "worker", "")
    92  	s.clock.Advance(time.Minute)
    93  
    94  	output, err := s.lock.Report()
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	c.Assert(output, gc.Equals, `
    97  test:
    98    holder: none
    99    waiting:
   100    - worker1 (being busy), waiting 2m0s
   101    - worker, waiting 1m0s
   102  `[1:])
   103  
   104  	output, err = s.lock.Report(machinelock.ShowDetailsYAML)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	c.Assert(output, gc.Equals, `
   107  test:
   108    holder: null
   109    waiting:
   110    - worker: worker1
   111      comment: being busy
   112      requested: 2018-07-10 12:00:00 +0000 UTC
   113      wait-time: 2m0s
   114    - worker: worker
   115      requested: 2018-07-10 12:01:00 +0000 UTC
   116      wait-time: 1m0s
   117  `[1:])
   118  }
   119  
   120  func (s *lockSuite) TestHoldingOutput(c *gc.C) {
   121  	s.addAcquired(c, "machine-lock", "", "worker1", "being busy", 0)
   122  	s.clock.Advance(time.Minute * 2)
   123  
   124  	output, err := s.lock.Report()
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(output, gc.Equals, `
   127  test:
   128    holder: worker1 (being busy), holding 2m0s
   129  `[1:])
   130  
   131  	output, err = s.lock.Report(machinelock.ShowDetailsYAML)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	c.Assert(output, gc.Equals, `
   134  test:
   135    holder:
   136      worker: worker1
   137      comment: being busy
   138      requested: 2018-07-10 12:00:00 +0000 UTC
   139      acquired: 2018-07-10 12:00:00 +0000 UTC
   140      hold-time: 2m0s
   141  `[1:])
   142  
   143  }
   144  
   145  func (s *lockSuite) TestLockGroup(c *gc.C) {
   146  	s.addAcquired(c, "machine-lock-group", "group", "worker1", "being busy", 0)
   147  	s.clock.Advance(time.Minute * 2)
   148  
   149  	output, err := s.lock.Report()
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	c.Assert(output, gc.Equals, `
   152  test:
   153    holder: worker1 (being busy), holding 2m0s
   154  `[1:])
   155  }
   156  
   157  func (s *lockSuite) TestHistoryOutput(c *gc.C) {
   158  	short := 5 * time.Second
   159  	long := 2*time.Minute + short
   160  	s.addHistory(c, "uniter", "config-changed", "2018-07-21 15:36:01", time.Second, long)
   161  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:37:05", time.Second, short)
   162  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:42:11", time.Second, short)
   163  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:47:13", time.Second, short)
   164  
   165  	output, err := s.lock.Report()
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	c.Assert(output, gc.Equals, `
   168  test:
   169    holder: none
   170  `[1:])
   171  
   172  	output, err = s.lock.Report(machinelock.ShowHistory)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	c.Assert(output, gc.Equals, `
   175  test:
   176    holder: none
   177    history:
   178    - 2018-07-21 15:47:13 uniter (update-status), waited 1s, held 5s
   179    - 2018-07-21 15:42:11 uniter (update-status), waited 1s, held 5s
   180    - 2018-07-21 15:37:05 uniter (update-status), waited 1s, held 5s
   181    - 2018-07-21 15:36:01 uniter (config-changed), waited 1s, held 2m5s
   182  `[1:])
   183  
   184  	output, err = s.lock.Report(machinelock.ShowHistory, machinelock.ShowDetailsYAML)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	c.Assert(output, gc.Equals, `
   187  test:
   188    holder: null
   189    history:
   190    - worker: uniter
   191      comment: update-status
   192      requested: 2018-07-21 15:47:07 +0000 UTC
   193      acquired: 2018-07-21 15:47:08 +0000 UTC
   194      released: 2018-07-21 15:47:13 +0000 UTC
   195      wait-time: 1s
   196      hold-time: 5s
   197    - worker: uniter
   198      comment: update-status
   199      requested: 2018-07-21 15:42:05 +0000 UTC
   200      acquired: 2018-07-21 15:42:06 +0000 UTC
   201      released: 2018-07-21 15:42:11 +0000 UTC
   202      wait-time: 1s
   203      hold-time: 5s
   204    - worker: uniter
   205      comment: update-status
   206      requested: 2018-07-21 15:36:59 +0000 UTC
   207      acquired: 2018-07-21 15:37:00 +0000 UTC
   208      released: 2018-07-21 15:37:05 +0000 UTC
   209      wait-time: 1s
   210      hold-time: 5s
   211    - worker: uniter
   212      comment: config-changed
   213      requested: 2018-07-21 15:33:55 +0000 UTC
   214      acquired: 2018-07-21 15:33:56 +0000 UTC
   215      released: 2018-07-21 15:36:01 +0000 UTC
   216      wait-time: 1s
   217      hold-time: 2m5s
   218  `[1:])
   219  }
   220  
   221  func (s *lockSuite) TestLogfileOutput(c *gc.C) {
   222  	short := 5 * time.Second
   223  	long := 2*time.Minute + short
   224  	s.addHistory(c, "uniter", "config-changed", "2018-07-21 15:36:01", time.Second, long)
   225  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:37:05", time.Second, short)
   226  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:42:11", time.Second, short)
   227  	s.addHistory(c, "uniter", "update-status", "2018-07-21 15:47:13", time.Second, short)
   228  
   229  	content, err := os.ReadFile(s.logfile)
   230  	c.Assert(err, jc.ErrorIsNil)
   231  
   232  	c.Assert(string(content), gc.Equals, `
   233  2018-07-10 12:00:00 === agent test started ===
   234  2018-07-21 15:36:01 test: uniter (config-changed), waited 1s, held 2m5s
   235  2018-07-21 15:37:05 test: uniter (update-status), waited 1s, held 5s
   236  2018-07-21 15:42:11 test: uniter (update-status), waited 1s, held 5s
   237  2018-07-21 15:47:13 test: uniter (update-status), waited 1s, held 5s
   238  `[1:])
   239  }
   240  
   241  func (s *lockSuite) addWaiting(c *gc.C, worker, comment string) {
   242  	go func() {
   243  		_, err := s.lock.Acquire(machinelock.Spec{
   244  			Cancel:  make(chan struct{}),
   245  			Worker:  worker,
   246  			Comment: comment,
   247  		})
   248  		c.Check(err, jc.ErrorIsNil)
   249  	}()
   250  
   251  	select {
   252  	case <-s.notify:
   253  	case <-time.After(jujutesting.LongWait):
   254  		c.Fatal("lock acquire didn't happen")
   255  	}
   256  }
   257  
   258  func (s *lockSuite) addAcquired(c *gc.C, name, group, worker, comment string, wait time.Duration) func() {
   259  	releaser := make(chan func())
   260  	go func() {
   261  		r, err := s.lock.Acquire(machinelock.Spec{
   262  			Cancel:  make(chan struct{}),
   263  			Worker:  worker,
   264  			Comment: comment,
   265  			Group:   group,
   266  		})
   267  		c.Check(err, jc.ErrorIsNil)
   268  		releaser <- r
   269  	}()
   270  
   271  	select {
   272  	case got := <-s.notify:
   273  		c.Assert(got, gc.Equals, name)
   274  	case <-time.After(jujutesting.LongWait):
   275  		c.Fatal("lock acquire didn't happen")
   276  	}
   277  	s.clock.Advance(wait)
   278  	select {
   279  	case s.allowAcquire <- struct{}{}:
   280  	case <-time.After(jujutesting.LongWait):
   281  		c.Fatal("lock acquire didn't advance")
   282  	}
   283  	select {
   284  	case r := <-releaser:
   285  		return r
   286  	case <-time.After(jujutesting.LongWait):
   287  		c.Fatal("no releaser returned")
   288  	}
   289  	panic("unreachable")
   290  }
   291  
   292  // This method needs the released time to be after the current suite clock time.
   293  func (s *lockSuite) addHistory(c *gc.C, worker, comment string, released string, waited, held time.Duration) {
   294  	releasedTime, err := time.Parse("2006-01-02 15:04:05", released)
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	// First, advance the lock to the request time.
   297  	diff := releasedTime.Sub(s.clock.Now())
   298  	diff -= waited + held
   299  	s.clock.Advance(diff)
   300  	releaser := s.addAcquired(c, "machine-lock", "", worker, comment, waited)
   301  	s.clock.Advance(held)
   302  	releaser()
   303  }
   304  
   305  func (s *lockSuite) acquireLock(spec mutex.Spec) (mutex.Releaser, error) {
   306  	s.notify <- spec.Name
   307  	select {
   308  	case <-s.allowAcquire:
   309  	case <-spec.Cancel:
   310  		return nil, errors.New("cancelled")
   311  	}
   312  	return noOpReleaser{}, nil
   313  }
   314  
   315  type noOpReleaser struct{}
   316  
   317  func (noOpReleaser) Release() {}
   318  
   319  type fakeClock struct {
   320  	now time.Time
   321  }
   322  
   323  func (f *fakeClock) Now() time.Time {
   324  	return f.now
   325  }
   326  
   327  func (f *fakeClock) Advance(d time.Duration) {
   328  	f.now = f.now.Add(d)
   329  }
   330  
   331  // This function is necessary for the interface that the mutex package
   332  // requires for the clock, but this isn't used in this test's suite as
   333  // we are mocking out the acquire function.
   334  func (f *fakeClock) After(time.Duration) <-chan time.Time {
   335  	return nil
   336  }