github.com/cilium/cilium@v1.16.2/operator/api/server.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package api
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"syscall"
    12  
    13  	"github.com/cilium/hive/cell"
    14  	"github.com/go-openapi/loads"
    15  	"github.com/go-openapi/runtime"
    16  	"github.com/sirupsen/logrus"
    17  	"golang.org/x/sys/unix"
    18  
    19  	operatorApi "github.com/cilium/cilium/api/v1/operator/server"
    20  	"github.com/cilium/cilium/api/v1/operator/server/restapi"
    21  	"github.com/cilium/cilium/api/v1/operator/server/restapi/cluster"
    22  	"github.com/cilium/cilium/api/v1/operator/server/restapi/metrics"
    23  	"github.com/cilium/cilium/api/v1/operator/server/restapi/operator"
    24  	"github.com/cilium/cilium/pkg/api"
    25  	"github.com/cilium/cilium/pkg/hive"
    26  )
    27  
    28  type Server interface {
    29  	// Ports returns the ports at which the server is listening
    30  	Ports() []int
    31  }
    32  
    33  // params contains all the dependencies for the api server.
    34  // They will be provided through dependency injection.
    35  type params struct {
    36  	cell.In
    37  
    38  	Cfg Config
    39  
    40  	HealthHandler   operator.GetHealthzHandler
    41  	MetricsHandler  metrics.GetMetricsHandler
    42  	ClusterHandler  cluster.GetClusterHandler
    43  	OperatorAPISpec *operatorApi.Spec
    44  
    45  	Logger     logrus.FieldLogger
    46  	Lifecycle  cell.Lifecycle
    47  	Shutdowner hive.Shutdowner
    48  }
    49  
    50  type server struct {
    51  	*operatorApi.Server
    52  
    53  	logger     logrus.FieldLogger
    54  	shutdowner hive.Shutdowner
    55  
    56  	address  string
    57  	httpSrvs []httpServer
    58  
    59  	healthHandler  operator.GetHealthzHandler
    60  	metricsHandler metrics.GetMetricsHandler
    61  	clusterHandler cluster.GetClusterHandler
    62  	apiSpec        *operatorApi.Spec
    63  }
    64  
    65  type httpServer struct {
    66  	address  string
    67  	listener net.Listener
    68  	server   *http.Server
    69  }
    70  
    71  func newServer(
    72  	p params,
    73  ) (Server, error) {
    74  	server := &server{
    75  		logger:         p.Logger,
    76  		shutdowner:     p.Shutdowner,
    77  		address:        p.Cfg.OperatorAPIServeAddr,
    78  		healthHandler:  p.HealthHandler,
    79  		metricsHandler: p.MetricsHandler,
    80  		clusterHandler: p.ClusterHandler,
    81  		apiSpec:        p.OperatorAPISpec,
    82  	}
    83  	p.Lifecycle.Append(server)
    84  
    85  	return server, nil
    86  }
    87  
    88  func (s *server) Start(ctx cell.HookContext) error {
    89  	spec, err := loads.Analyzed(operatorApi.SwaggerJSON, "")
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	restAPI := restapi.NewCiliumOperatorAPI(spec)
    95  	restAPI.Logger = s.logger.Debugf
    96  	restAPI.OperatorGetHealthzHandler = s.healthHandler
    97  	restAPI.MetricsGetMetricsHandler = s.metricsHandler
    98  	restAPI.ClusterGetClusterHandler = s.clusterHandler
    99  
   100  	api.DisableAPIs(s.apiSpec.DeniedAPIs, restAPI.AddMiddlewareFor)
   101  	srv := operatorApi.NewServer(restAPI)
   102  	srv.EnabledListeners = []string{"http"}
   103  	srv.ConfigureAPI()
   104  	s.Server = srv
   105  
   106  	mux := http.NewServeMux()
   107  
   108  	// Index handler is the handler for Open-API router.
   109  	mux.Handle("/", s.Server.GetHandler())
   110  	// Create a custom handler for /healthz as an alias to /v1/healthz. A http mux
   111  	// is required for this because open-api spec does not allow multiple base paths
   112  	// to be specified.
   113  	mux.HandleFunc("/healthz", func(rw http.ResponseWriter, _ *http.Request) {
   114  		resp := s.healthHandler.Handle(operator.GetHealthzParams{})
   115  		resp.WriteResponse(rw, runtime.TextProducer())
   116  	})
   117  
   118  	if s.address == "" {
   119  		// Since we are opening this on localhost only, we need to make sure
   120  		// we can open for both v4 and v6 localhost, in case the user is running
   121  		// v4-only or v6-only.
   122  		s.httpSrvs = make([]httpServer, 2)
   123  		s.httpSrvs[0].address = "127.0.0.1:0"
   124  		s.httpSrvs[1].address = "[::1]:0"
   125  	} else {
   126  		s.httpSrvs = make([]httpServer, 1)
   127  		s.httpSrvs[0].address = s.address
   128  	}
   129  
   130  	var errs []error
   131  	for i := range s.httpSrvs {
   132  		lc := net.ListenConfig{Control: setsockoptReuseAddrAndPort}
   133  		ln, err := lc.Listen(ctx, "tcp", s.httpSrvs[i].address)
   134  		if err != nil {
   135  			errs = append(errs, fmt.Errorf("unable to listen on %s: %w", s.httpSrvs[i].address, err))
   136  			continue
   137  		}
   138  		s.httpSrvs[i].listener = ln
   139  
   140  		s.logger.WithFields(logrus.Fields{
   141  			fmt.Sprintf("address-%d", i): ln.Addr().String(),
   142  		})
   143  
   144  		s.httpSrvs[i].server = &http.Server{
   145  			Addr:    s.httpSrvs[i].address,
   146  			Handler: mux,
   147  		}
   148  	}
   149  
   150  	// if no apiserver can be started, we stop the cell
   151  	if (len(s.httpSrvs) == 1 && s.httpSrvs[0].server == nil) ||
   152  		(len(s.httpSrvs) == 2 && s.httpSrvs[0].server == nil && s.httpSrvs[1].server == nil) {
   153  		s.shutdowner.Shutdown()
   154  		return errors.Join(errs...)
   155  	}
   156  
   157  	// otherwise just log any possible error and continue
   158  	for _, err := range errs {
   159  		s.logger.WithError(err).Error("apiserver start failed")
   160  	}
   161  
   162  	for _, srv := range s.httpSrvs {
   163  		if srv.server == nil {
   164  			continue
   165  		}
   166  		go func(srv httpServer) {
   167  			if err := srv.server.Serve(srv.listener); !errors.Is(err, http.ErrServerClosed) {
   168  				s.logger.WithError(err).Error("server stopped unexpectedly")
   169  				s.shutdowner.Shutdown()
   170  			}
   171  		}(srv)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  // setsockoptReuseAddrAndPort sets the SO_REUSEADDR and SO_REUSEPORT socket options on c's
   178  // underlying socket in order to improve the chance to re-bind to the same address and port
   179  // upon restart.
   180  func (s *server) Stop(ctx cell.HookContext) error {
   181  	for _, srv := range s.httpSrvs {
   182  		if srv.server == nil {
   183  			continue
   184  		}
   185  		if err := srv.server.Shutdown(ctx); err != nil {
   186  			return err
   187  		}
   188  	}
   189  	return nil
   190  }
   191  
   192  func (s *server) Ports() []int {
   193  	ports := make([]int, 0, len(s.httpSrvs))
   194  	for _, srv := range s.httpSrvs {
   195  		if srv.server == nil {
   196  			continue
   197  		}
   198  		ports = append(ports, srv.listener.Addr().(*net.TCPAddr).Port)
   199  	}
   200  	return ports
   201  }
   202  
   203  // setsockoptReuseAddrAndPort sets SO_REUSEADDR and SO_REUSEPORT
   204  func setsockoptReuseAddrAndPort(network, address string, c syscall.RawConn) error {
   205  	var soerr error
   206  	if err := c.Control(func(su uintptr) {
   207  		s := int(su)
   208  		// Allow reuse of recently-used addresses. This socket option is
   209  		// set by default on listeners in Go's net package, see
   210  		// net setDefaultListenerSockopts
   211  		if err := unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil {
   212  			soerr = fmt.Errorf("failed to setsockopt(SO_REUSEADDR): %w", err)
   213  			return
   214  		}
   215  
   216  		// Allow reuse of recently-used ports. This gives the operator a
   217  		// better chance to re-bind upon restarts.
   218  		soerr = unix.SetsockoptInt(s, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
   219  	}); err != nil {
   220  		return err
   221  	}
   222  	return soerr
   223  }