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 }