github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/pebblepoller_test.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "context" 8 "regexp" 9 "sync" 10 "time" 11 12 pebbleclient "github.com/canonical/pebble/client" 13 "github.com/juju/clock/testclock" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils/v3" 18 "github.com/juju/worker/v3/workertest" 19 gc "gopkg.in/check.v1" 20 21 "github.com/juju/juju/testing" 22 "github.com/juju/juju/worker/uniter" 23 "github.com/juju/juju/worker/uniter/container" 24 ) 25 26 type pebblePollerSuite struct{} 27 28 var _ = gc.Suite(&pebblePollerSuite{}) 29 30 const ( 31 pebbleSocketPathRegexpString = "/charm/containers/([^/]+)/pebble.socket" 32 ) 33 34 var ( 35 pebbleSocketPathRegexp = regexp.MustCompile(pebbleSocketPathRegexpString) 36 ) 37 38 func (s *pebblePollerSuite) TestStart(c *gc.C) { 39 clients := map[string]*fakePebbleClient{ 40 "a": { 41 sysInfo: pebbleclient.SysInfo{ 42 BootID: "1", 43 }, 44 err: errors.Errorf("not yet workin"), 45 }, 46 "b": { 47 sysInfo: pebbleclient.SysInfo{ 48 BootID: "1", 49 }, 50 err: errors.Errorf("not yet workin"), 51 }, 52 "c": { 53 sysInfo: pebbleclient.SysInfo{ 54 BootID: "1", 55 }, 56 err: errors.Errorf("not yet workin"), 57 }, 58 } 59 newClient := func(cfg *pebbleclient.Config) (uniter.PebbleClient, error) { 60 c.Assert(cfg.Socket, gc.Matches, pebbleSocketPathRegexpString) 61 res := pebbleSocketPathRegexp.FindAllStringSubmatch(cfg.Socket, 1) 62 return clients[res[0][1]], nil 63 } 64 clock := testclock.NewClock(time.Time{}) 65 containerNames := []string{ 66 "a", "b", "c", 67 } 68 workloadEventChan := make(chan string) 69 workloadEvents := container.NewWorkloadEvents() 70 worker := uniter.NewPebblePoller(loggo.GetLogger("test"), clock, containerNames, workloadEventChan, workloadEvents, newClient) 71 72 doRestart := func(containerName string) { 73 client := clients[containerName] 74 c.Assert(workloadEvents.Events(), gc.HasLen, 0) 75 client.TriggerStart() 76 timeout := time.After(testing.LongWait) 77 for { 78 select { 79 case id := <-workloadEventChan: 80 c.Logf("got queued log id %s", id) 81 c.Assert(workloadEvents.Events(), gc.HasLen, 1) 82 evt, cb, err := workloadEvents.GetWorkloadEvent(id) 83 c.Assert(err, jc.ErrorIsNil) 84 c.Assert(evt, gc.DeepEquals, container.WorkloadEvent{ 85 Type: container.ReadyEvent, 86 WorkloadName: containerName, 87 }) 88 c.Assert(cb, gc.NotNil) 89 workloadEvents.RemoveWorkloadEvent(id) 90 cb(nil) 91 c.Assert(workloadEvents.Events(), gc.HasLen, 0) 92 return 93 case <-time.After(testing.ShortWait): 94 clock.Advance(5 * time.Second) 95 case <-timeout: 96 c.Fatalf("timed out waiting for event id") 97 return 98 } 99 } 100 } 101 102 doRestart("a") 103 doRestart("b") 104 doRestart("c") 105 doRestart("a") 106 doRestart("a") 107 doRestart("a") 108 109 workertest.CleanKill(c, worker) 110 111 for k, v := range clients { 112 c.Assert(v.closed, jc.IsTrue, gc.Commentf("client %s not closed", k)) 113 } 114 } 115 116 type fakePebbleClient struct { 117 sysInfo pebbleclient.SysInfo 118 err error 119 mut sync.Mutex 120 closed bool 121 clock *testclock.Clock 122 noticeAdded chan *pebbleclient.Notice 123 } 124 125 func (c *fakePebbleClient) SysInfo() (*pebbleclient.SysInfo, error) { 126 c.mut.Lock() 127 defer c.mut.Unlock() 128 if c.err != nil { 129 return nil, c.err 130 } 131 sysInfoCopy := c.sysInfo 132 return &sysInfoCopy, nil 133 } 134 135 func (c *fakePebbleClient) TriggerStart() { 136 c.mut.Lock() 137 defer c.mut.Unlock() 138 c.err = nil 139 c.sysInfo.BootID = utils.MustNewUUID().String() 140 } 141 142 func (c *fakePebbleClient) CloseIdleConnections() { 143 c.mut.Lock() 144 defer c.mut.Unlock() 145 c.closed = true 146 } 147 148 // AddNotice adds a notice for WaitNotices to receive. To have WaitNotices 149 // return an error, use notice.Type "error" with the message in notice.Key. 150 func (c *fakePebbleClient) AddNotice(checkC *gc.C, notice *pebbleclient.Notice) { 151 select { 152 case c.noticeAdded <- notice: 153 case <-time.After(testing.LongWait): 154 checkC.Fatalf("timed out waiting to add notice") 155 } 156 } 157 158 func (c *fakePebbleClient) WaitNotices(ctx context.Context, serverTimeout time.Duration, opts *pebbleclient.NoticesOptions) ([]*pebbleclient.Notice, error) { 159 timeoutCh := c.clock.After(serverTimeout) 160 for { 161 select { 162 case notice := <-c.noticeAdded: 163 if notice.Type == "error" { 164 return nil, errors.New(notice.Key) 165 } 166 if noticeMatches(notice, opts) { 167 return []*pebbleclient.Notice{notice}, nil 168 } 169 case <-timeoutCh: 170 return nil, nil // no notices after serverTimeout is not an error 171 case <-ctx.Done(): 172 return nil, ctx.Err() 173 } 174 } 175 } 176 177 func noticeMatches(notice *pebbleclient.Notice, opts *pebbleclient.NoticesOptions) bool { 178 if opts == nil || opts.Types != nil || opts.Keys != nil { 179 panic("not supported") 180 } 181 if !opts.After.IsZero() && !notice.LastRepeated.After(opts.After) { 182 return false 183 } 184 return true 185 }