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  }