github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net" 8 "net/http" 9 "net/http/pprof" 10 "os" 11 "runtime" 12 "strings" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/hanks177/podman/v4/libpod" 18 "github.com/hanks177/podman/v4/libpod/shutdown" 19 "github.com/hanks177/podman/v4/pkg/api/handlers" 20 "github.com/hanks177/podman/v4/pkg/api/server/idle" 21 "github.com/hanks177/podman/v4/pkg/api/types" 22 "github.com/hanks177/podman/v4/pkg/domain/entities" 23 "github.com/coreos/go-systemd/v22/daemon" 24 "github.com/gorilla/mux" 25 "github.com/gorilla/schema" 26 "github.com/sirupsen/logrus" 27 ) 28 29 type APIServer struct { 30 http.Server // The HTTP work happens here 31 net.Listener // mux for routing HTTP API calls to libpod routines 32 *libpod.Runtime // Where the real work happens 33 *schema.Decoder // Decoder for Query parameters to structs 34 context.CancelFunc // Stop APIServer 35 context.Context // Context to carry objects to handlers 36 CorsHeaders string // Inject Cross-Origin Resource Sharing (CORS) headers 37 PProfAddr string // Binding network address for pprof profiles 38 idleTracker *idle.Tracker // Track connections to support idle shutdown 39 } 40 41 // Number of seconds to wait for next request, if exceeded shutdown server 42 const ( 43 DefaultCorsHeaders = "" 44 DefaultServiceDuration = 300 * time.Second 45 UnlimitedServiceDuration = 0 * time.Second 46 ) 47 48 // shutdownOnce ensures Shutdown() may safely be called from several go routines 49 var shutdownOnce sync.Once 50 51 // NewServer will create and configure a new API server with all defaults 52 func NewServer(runtime *libpod.Runtime) (*APIServer, error) { 53 return newServer(runtime, nil, entities.ServiceOptions{ 54 CorsHeaders: DefaultCorsHeaders, 55 Timeout: DefaultServiceDuration, 56 }) 57 } 58 59 // NewServerWithSettings will create and configure a new API server using provided settings 60 func NewServerWithSettings(runtime *libpod.Runtime, listener net.Listener, opts entities.ServiceOptions) (*APIServer, error) { 61 return newServer(runtime, listener, opts) 62 } 63 64 func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.ServiceOptions) (*APIServer, error) { 65 logrus.Infof("API service listening on %q. URI: %q", listener.Addr(), runtime.RemoteURI()) 66 if opts.CorsHeaders == "" { 67 logrus.Debug("CORS Headers were not set") 68 } else { 69 logrus.Debugf("CORS Headers were set to %q", opts.CorsHeaders) 70 } 71 72 logrus.Infof("API service listening on %q", listener.Addr()) 73 router := mux.NewRouter().UseEncodedPath() 74 tracker := idle.NewTracker(opts.Timeout) 75 76 server := APIServer{ 77 Server: http.Server{ 78 ConnContext: func(ctx context.Context, c net.Conn) context.Context { 79 return context.WithValue(ctx, types.ConnKey, c) 80 }, 81 ConnState: tracker.ConnState, 82 ErrorLog: log.New(logrus.StandardLogger().Out, "", 0), 83 Handler: router, 84 IdleTimeout: opts.Timeout * 2, 85 }, 86 CorsHeaders: opts.CorsHeaders, 87 Listener: listener, 88 PProfAddr: opts.PProfAddr, 89 idleTracker: tracker, 90 } 91 92 server.BaseContext = func(l net.Listener) context.Context { 93 ctx := context.WithValue(context.Background(), types.DecoderKey, handlers.NewAPIDecoder()) 94 ctx = context.WithValue(ctx, types.RuntimeKey, runtime) 95 ctx = context.WithValue(ctx, types.IdleTrackerKey, tracker) 96 return ctx 97 } 98 99 // Capture panics and print stack traces for diagnostics, 100 // additionally process X-Reference-Id Header to support event correlation 101 router.Use(panicHandler(), referenceIDHandler()) 102 router.NotFoundHandler = http.HandlerFunc( 103 func(w http.ResponseWriter, r *http.Request) { 104 // We can track user errors... 105 logrus.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusNotFound, http.StatusText(http.StatusNotFound), r.Method, r.URL.String()) 106 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 107 }, 108 ) 109 110 router.MethodNotAllowedHandler = http.HandlerFunc( 111 func(w http.ResponseWriter, r *http.Request) { 112 // We can track user errors... 113 logrus.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusMethodNotAllowed, http.StatusText(http.StatusMethodNotAllowed), r.Method, r.URL.String()) 114 http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) 115 }, 116 ) 117 118 for _, fn := range []func(*mux.Router) error{ 119 server.registerAuthHandlers, 120 server.registerArchiveHandlers, 121 server.registerContainersHandlers, 122 server.registerDistributionHandlers, 123 server.registerEventsHandlers, 124 server.registerExecHandlers, 125 server.registerGenerateHandlers, 126 server.registerHealthCheckHandlers, 127 server.registerImagesHandlers, 128 server.registerInfoHandlers, 129 server.registerManifestHandlers, 130 server.registerMonitorHandlers, 131 server.registerNetworkHandlers, 132 server.registerPingHandlers, 133 server.registerPlayHandlers, 134 server.registerPluginsHandlers, 135 server.registerPodsHandlers, 136 server.registerSecretHandlers, 137 server.registerSwaggerHandlers, 138 server.registerSwarmHandlers, 139 server.registerSystemHandlers, 140 server.registerVersionHandlers, 141 server.registerVolumeHandlers, 142 } { 143 if err := fn(router); err != nil { 144 return nil, err 145 } 146 } 147 148 if logrus.IsLevelEnabled(logrus.TraceLevel) { 149 // If in trace mode log request and response bodies 150 router.Use(loggingHandler()) 151 router.Walk(func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error { // nolint 152 path, err := route.GetPathTemplate() 153 if err != nil { 154 path = "<N/A>" 155 } 156 methods, err := route.GetMethods() 157 if err != nil { 158 methods = []string{"<N/A>"} 159 } 160 logrus.Tracef("Methods: %6s Path: %s", strings.Join(methods, ", "), path) 161 return nil 162 }) 163 } 164 165 return &server, nil 166 } 167 168 // setupSystemd notifies systemd API service is ready 169 // If the NOTIFY_SOCKET is set, communicate the PID and readiness, and unset INVOCATION_ID 170 // so conmon and containers are in the correct cgroup. 171 func (s *APIServer) setupSystemd() { 172 if _, found := os.LookupEnv("NOTIFY_SOCKET"); !found { 173 return 174 } 175 176 payload := fmt.Sprintf("MAINPID=%d\n", os.Getpid()) 177 payload += daemon.SdNotifyReady 178 if sent, err := daemon.SdNotify(true, payload); err != nil { 179 logrus.Error("API service failed to notify systemd of Conmon PID: " + err.Error()) 180 } else if !sent { 181 logrus.Warn("API service unable to successfully send SDNotify") 182 } 183 184 if err := os.Unsetenv("INVOCATION_ID"); err != nil { 185 logrus.Error("API service failed unsetting INVOCATION_ID: " + err.Error()) 186 } 187 } 188 189 // Serve starts responding to HTTP requests. 190 func (s *APIServer) Serve() error { 191 s.setupPprof() 192 193 if err := shutdown.Register("service", func(sig os.Signal) error { 194 return s.Shutdown(true) 195 }); err != nil { 196 return err 197 } 198 // Start the shutdown signal handler. 199 if err := shutdown.Start(); err != nil { 200 return err 201 } 202 203 go func() { 204 <-s.idleTracker.Done() 205 logrus.Debugf("API service(s) shutting down, idle for %ds", int(s.idleTracker.Duration.Seconds())) 206 _ = s.Shutdown(false) 207 }() 208 209 // Before we start serving, ensure umask is properly set for container creation. 210 _ = syscall.Umask(0o022) 211 212 errChan := make(chan error, 1) 213 s.setupSystemd() 214 go func() { 215 err := s.Server.Serve(s.Listener) 216 if err != nil && err != http.ErrServerClosed { 217 errChan <- fmt.Errorf("failed to start API service: %w", err) 218 return 219 } 220 errChan <- nil 221 }() 222 223 return <-errChan 224 } 225 226 // setupPprof enables pprof default endpoints 227 // Note: These endpoints and the podman flag --cpu-profile are mutually exclusive 228 // 229 // Examples: 230 // #1 go tool pprof -http localhost:8889 localhost:8888/debug/pprof/heap?seconds=120 231 // Note: web page will only render after a sample has been recorded 232 // #2 curl http://localhost:8888/debug/pprof/heap > heap.pprof && go tool pprof heap.pprof 233 func (s *APIServer) setupPprof() { 234 if s.PProfAddr == "" { 235 return 236 } 237 238 logrus.Infof("pprof service listening on %q", s.PProfAddr) 239 go func() { 240 old := runtime.SetMutexProfileFraction(1) 241 defer runtime.SetMutexProfileFraction(old) 242 243 runtime.SetBlockProfileRate(1) 244 defer runtime.SetBlockProfileRate(0) 245 246 router := mux.NewRouter() 247 router.PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index) 248 249 err := http.ListenAndServe(s.PProfAddr, router) 250 if err != nil && err != http.ErrServerClosed { 251 logrus.Warnf("pprof service failed: %v", err) 252 } 253 }() 254 } 255 256 // Shutdown is a clean shutdown waiting on existing clients 257 func (s *APIServer) Shutdown(halt bool) error { 258 switch { 259 case halt: 260 logrus.Debug("API service forced shutdown, ignoring timeout Duration") 261 case s.idleTracker.Duration == UnlimitedServiceDuration: 262 logrus.Debug("API service shutdown request ignored as timeout Duration is UnlimitedService") 263 return nil 264 } 265 266 shutdownOnce.Do(func() { 267 logrus.Debugf("API service shutdown, %d/%d connection(s)", 268 s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections()) 269 270 // Gracefully shutdown server(s), duration of wait same as idle window 271 deadline := 1 * time.Second 272 if s.idleTracker.Duration > 0 { 273 deadline = s.idleTracker.Duration 274 } 275 ctx, cancel := context.WithTimeout(context.Background(), deadline) 276 go func() { 277 defer cancel() 278 279 err := s.Server.Shutdown(ctx) 280 if err != nil && err != context.Canceled && err != http.ErrServerClosed { 281 logrus.Error("Failed to cleanly shutdown API service: " + err.Error()) 282 } 283 }() 284 <-ctx.Done() 285 }) 286 return nil 287 } 288 289 // Close immediately stops responding to clients and exits 290 func (s *APIServer) Close() error { 291 return s.Server.Close() 292 }