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 }