github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/controlsocket/worker.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 //go:build linux 5 6 package controlsocket 7 8 import ( 9 "context" 10 "net" 11 "net/http" 12 "time" 13 14 "github.com/gorilla/mux" 15 "github.com/juju/errors" 16 "github.com/juju/worker/v3" 17 "gopkg.in/tomb.v2" 18 19 "github.com/juju/juju/juju/sockets" 20 ) 21 22 // logger is here to stop the desire of creating a package level logger. 23 // Don't do this, instead use the one passed as manifold config. 24 type logger any 25 26 var _ logger = struct{}{} 27 28 // Logger represents the methods used by the worker to log information. 29 type Logger interface { 30 Errorf(string, ...any) 31 Warningf(string, ...any) 32 Infof(string, ...any) 33 Debugf(string, ...any) 34 Tracef(string, ...any) 35 } 36 37 // Config represents configuration for the controlsocket worker. 38 type Config struct { 39 State State 40 Logger Logger 41 SocketName string 42 } 43 44 // Validate returns an error if config cannot drive the Worker. 45 func (config Config) Validate() error { 46 if config.State == nil { 47 return errors.NotValidf("nil State") 48 } 49 if config.Logger == nil { 50 return errors.NotValidf("nil Logger") 51 } 52 if config.SocketName == "" { 53 return errors.NotValidf("empty SocketName") 54 } 55 return nil 56 } 57 58 // Worker is a controlsocket worker. 59 type Worker struct { 60 config Config 61 tomb tomb.Tomb 62 listener net.Listener 63 } 64 65 // NewWorker returns a controlsocket worker with the given config. 66 func NewWorker(config Config) (worker.Worker, error) { 67 if err := config.Validate(); err != nil { 68 return nil, errors.Trace(err) 69 } 70 71 l, err := sockets.Listen(sockets.Socket{ 72 Address: config.SocketName, 73 Network: "unix", 74 }) 75 if err != nil { 76 return nil, errors.Annotate(err, "unable to listen on unix socket") 77 } 78 config.Logger.Debugf("controlsocket worker listening on socket %q", config.SocketName) 79 80 w := &Worker{ 81 config: config, 82 listener: l, 83 } 84 w.tomb.Go(w.run) 85 return w, nil 86 } 87 88 func (w *Worker) Kill() { 89 w.tomb.Kill(nil) 90 } 91 92 func (w *Worker) Wait() error { 93 return w.tomb.Wait() 94 } 95 96 // run listens on the control socket and handles requests. 97 func (w *Worker) run() error { 98 router := mux.NewRouter() 99 w.registerHandlers(router) 100 101 srv := http.Server{Handler: router} 102 defer func() { 103 err := srv.Close() 104 if err != nil { 105 w.config.Logger.Warningf("error closing HTTP server: %v", err) 106 } 107 }() 108 109 go func() { 110 // Wait for the tomb to start dying and then shut the server down. 111 <-w.tomb.Dying() 112 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 113 defer cancel() 114 if err := srv.Shutdown(ctx); err != nil { 115 w.config.Logger.Warningf("error shutting down HTTP server: %v", err) 116 } 117 }() 118 119 w.config.Logger.Debugf("controlsocket worker now serving") 120 defer w.config.Logger.Debugf("controlsocket worker serving finished") 121 if err := srv.Serve(w.listener); err != http.ErrServerClosed { 122 return err 123 } 124 return nil 125 }