github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/pebblepoller.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "context" 8 "path" 9 "sync" 10 "time" 11 12 "github.com/canonical/pebble/client" 13 "github.com/juju/clock" 14 "github.com/juju/errors" 15 "github.com/juju/worker/v3" 16 "gopkg.in/tomb.v2" 17 18 "github.com/juju/juju/worker/uniter/container" 19 ) 20 21 // PebbleClient describes the subset of github.com/canonical/pebble/client.Client that we 22 // need for the PebblePoller. 23 type PebbleClient interface { 24 CloseIdleConnections() 25 SysInfo() (*client.SysInfo, error) 26 WaitNotices(ctx context.Context, serverTimeout time.Duration, opts *client.NoticesOptions) ([]*client.Notice, error) 27 } 28 29 // NewPebbleClientFunc is the function type used to create a PebbleClient. 30 type NewPebbleClientFunc func(*client.Config) (PebbleClient, error) 31 32 type pebblePoller struct { 33 logger Logger 34 clock clock.Clock 35 tomb tomb.Tomb 36 newPebbleClient NewPebbleClientFunc 37 38 workloadEventChan chan string 39 workloadEvents container.WorkloadEvents 40 41 mut sync.Mutex 42 pebbleBootIDs map[string]string 43 } 44 45 const ( 46 pebblePollInterval = 5 * time.Second 47 ) 48 49 // NewPebblePoller starts a worker that polls the pebble interfaces 50 // of the supplied container list. 51 func NewPebblePoller(logger Logger, 52 clock clock.Clock, 53 containerNames []string, 54 workloadEventChan chan string, 55 workloadEvents container.WorkloadEvents, 56 newPebbleClient NewPebbleClientFunc) worker.Worker { 57 if newPebbleClient == nil { 58 newPebbleClient = func(config *client.Config) (PebbleClient, error) { 59 return client.New(config) 60 } 61 } 62 p := &pebblePoller{ 63 logger: logger, 64 clock: clock, 65 workloadEventChan: workloadEventChan, 66 workloadEvents: workloadEvents, 67 newPebbleClient: newPebbleClient, 68 pebbleBootIDs: make(map[string]string), 69 } 70 for _, v := range containerNames { 71 containerName := v 72 p.tomb.Go(func() error { 73 return p.run(containerName) 74 }) 75 } 76 return p 77 } 78 79 // Kill is part of the worker.Worker interface. 80 func (p *pebblePoller) Kill() { 81 p.tomb.Kill(nil) 82 } 83 84 // Wait is part of the worker.Worker interface. 85 func (p *pebblePoller) Wait() error { 86 return p.tomb.Wait() 87 } 88 89 func (p *pebblePoller) run(containerName string) error { 90 timer := p.clock.NewTimer(pebblePollInterval) 91 defer timer.Stop() 92 for { 93 select { 94 case <-p.tomb.Dying(): 95 return tomb.ErrDying 96 case <-timer.Chan(): 97 timer.Reset(pebblePollInterval) 98 err := p.poll(containerName) 99 var socketNotFound *client.SocketNotFoundError 100 if errors.As(err, &socketNotFound) { 101 p.logger.Debugf("pebble still starting up on container %q: %v", containerName, socketNotFound) 102 } else if err != nil && err != tomb.ErrDying { 103 p.logger.Errorf("pebble poll failed for container %q: %v", containerName, err) 104 } 105 } 106 } 107 } 108 109 func newPebbleConfig(containerName string) *client.Config { 110 return &client.Config{ 111 Socket: path.Join("/charm/containers", containerName, "pebble.socket"), 112 } 113 } 114 115 func (p *pebblePoller) poll(containerName string) error { 116 config := newPebbleConfig(containerName) 117 pc, err := p.newPebbleClient(config) 118 if err != nil { 119 return errors.Annotate(err, "failed to create Pebble client") 120 } 121 defer pc.CloseIdleConnections() 122 info, err := pc.SysInfo() 123 if err != nil { 124 return errors.Annotate(err, "failed to get pebble info") 125 } 126 127 p.mut.Lock() 128 lastBootID, _ := p.pebbleBootIDs[containerName] 129 p.mut.Unlock() 130 if lastBootID == info.BootID { 131 // Boot ID is the same as last time, so it's normal poll and no 132 // pebble-ready event is needed. 133 return nil 134 } 135 136 // We've just started up, so send a pebble-ready event. 137 errChan := make(chan error, 1) 138 eid := p.workloadEvents.AddWorkloadEvent(container.WorkloadEvent{ 139 Type: container.ReadyEvent, 140 WorkloadName: containerName, 141 }, func(err error) { 142 errChan <- errors.Trace(err) 143 }) 144 defer p.workloadEvents.RemoveWorkloadEvent(eid) 145 146 select { 147 case p.workloadEventChan <- eid: 148 case <-p.tomb.Dying(): 149 return tomb.ErrDying 150 } 151 152 select { 153 case err := <-errChan: 154 if err != nil { 155 return errors.Annotate(err, "failed to send pebble-ready event") 156 } 157 case <-p.tomb.Dying(): 158 return tomb.ErrDying 159 } 160 161 p.mut.Lock() 162 p.pebbleBootIDs[containerName] = info.BootID 163 p.mut.Unlock() 164 165 return nil 166 }