github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/pebblenotices.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "time" 8 9 "github.com/canonical/pebble/client" 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/worker/v3" 13 "gopkg.in/tomb.v2" 14 15 "github.com/juju/juju/worker/uniter/container" 16 ) 17 18 type pebbleNoticer struct { 19 logger Logger 20 clock clock.Clock 21 workloadEventChan chan string 22 workloadEvents container.WorkloadEvents 23 newPebbleClient NewPebbleClientFunc 24 25 tomb tomb.Tomb 26 } 27 28 // NewPebbleNoticer starts a worker that watches for Pebble notices on the 29 // specified containers. 30 func NewPebbleNoticer( 31 logger Logger, 32 clock clock.Clock, 33 containerNames []string, 34 workloadEventChan chan string, 35 workloadEvents container.WorkloadEvents, 36 newPebbleClient NewPebbleClientFunc, 37 ) worker.Worker { 38 if newPebbleClient == nil { 39 newPebbleClient = func(config *client.Config) (PebbleClient, error) { 40 return client.New(config) 41 } 42 } 43 noticer := &pebbleNoticer{ 44 logger: logger, 45 clock: clock, 46 workloadEventChan: workloadEventChan, 47 workloadEvents: workloadEvents, 48 newPebbleClient: newPebbleClient, 49 } 50 for _, name := range containerNames { 51 name := name 52 noticer.tomb.Go(func() error { 53 return noticer.run(name) 54 }) 55 } 56 return noticer 57 } 58 59 // Kill is part of the worker.Worker interface. 60 func (n *pebbleNoticer) Kill() { 61 n.tomb.Kill(nil) 62 } 63 64 // Wait is part of the worker.Worker interface. 65 func (n *pebbleNoticer) Wait() error { 66 return n.tomb.Wait() 67 } 68 69 func (n *pebbleNoticer) run(containerName string) (err error) { 70 const ( 71 waitTimeout = 30 * time.Second 72 errorDelay = time.Second 73 ) 74 75 n.logger.Debugf("container %q: pebbleNoticer starting", containerName) 76 defer n.logger.Debugf("container %q: pebbleNoticer stopped, error %v", containerName, err) 77 78 config := newPebbleConfig(containerName) 79 pebbleClient, err := n.newPebbleClient(config) 80 if err != nil { 81 return errors.Trace(err) 82 } 83 defer pebbleClient.CloseIdleConnections() 84 85 var after time.Time 86 ctx := n.tomb.Context(nil) 87 for { 88 // Wait up to a timeout for new notices to arrive (also stop when 89 // tomb's context is cancelled). 90 options := &client.NoticesOptions{After: after} 91 notices, err := pebbleClient.WaitNotices(ctx, waitTimeout, options) 92 93 // Return early if the worker was killed. 94 select { 95 case <-n.tomb.Dying(): 96 return tomb.ErrDying 97 default: 98 } 99 100 // If an error occurred, wait a bit and try again. 101 if err != nil { 102 var socketNotFound *client.SocketNotFoundError 103 if errors.As(err, &socketNotFound) { 104 // Pebble has probably not started yet -- not an error. 105 n.logger.Debugf("container %q: socket %q not found, waiting %s", 106 containerName, socketNotFound.Path, errorDelay) 107 } else { 108 n.logger.Errorf("container %q: WaitNotices error, waiting %s: %v", 109 containerName, errorDelay, err) 110 } 111 select { 112 case <-n.clock.After(errorDelay): 113 case <-n.tomb.Dying(): 114 return tomb.ErrDying 115 } 116 continue 117 } 118 119 // Send any notices as Juju events. 120 for _, notice := range notices { 121 err := n.processNotice(containerName, notice) 122 if err != nil { 123 // Avoid wrapping or tracing this error, as processNotice can 124 // return tomb.ErrDying, and tomb doesn't use errors.Is yet. 125 return err 126 } 127 128 // Update the next "after" query time to the latest LastRepeated value. 129 after = notice.LastRepeated 130 } 131 } 132 } 133 134 func (n *pebbleNoticer) processNotice(containerName string, notice *client.Notice) error { 135 var eventType container.WorkloadEventType 136 switch notice.Type { 137 case client.CustomNotice: 138 eventType = container.CustomNoticeEvent 139 default: 140 n.logger.Debugf("container %q: ignoring %s notice", containerName, notice.Type) 141 return nil 142 } 143 144 n.logger.Debugf("container %q: processing %s notice, key %q", containerName, notice.Type, notice.Key) 145 146 errChan := make(chan error, 1) 147 eventID := n.workloadEvents.AddWorkloadEvent(container.WorkloadEvent{ 148 Type: eventType, 149 WorkloadName: containerName, 150 NoticeID: notice.ID, 151 NoticeType: string(notice.Type), 152 NoticeKey: notice.Key, 153 }, func(err error) { 154 errChan <- errors.Trace(err) 155 }) 156 defer n.workloadEvents.RemoveWorkloadEvent(eventID) 157 158 // Send the event to the charm! 159 select { 160 case n.workloadEventChan <- eventID: 161 case <-n.tomb.Dying(): 162 return tomb.ErrDying 163 } 164 165 select { 166 case err := <-errChan: 167 if err != nil { 168 return errors.Annotatef(err, "failed to send event for %s notice, key %q", 169 notice.Type, notice.Key) 170 } 171 case <-n.tomb.Dying(): 172 return tomb.ErrDying 173 } 174 175 return nil 176 }