istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/instance.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package server 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "net/netip" 23 "os" 24 "strings" 25 "sync" 26 "sync/atomic" 27 28 "github.com/hashicorp/go-multierror" 29 30 "istio.io/istio/pilot/pkg/util/network" 31 "istio.io/istio/pkg/config/protocol" 32 "istio.io/istio/pkg/log" 33 "istio.io/istio/pkg/monitoring" 34 "istio.io/istio/pkg/test/echo/common" 35 "istio.io/istio/pkg/test/echo/server/endpoint" 36 ) 37 38 // Config for an echo server Instance. 39 type Config struct { 40 Ports common.PortList 41 BindIPPortsMap map[int]struct{} 42 BindLocalhostPortsMap map[int]struct{} 43 Metrics int 44 TLSCert string 45 TLSKey string 46 Version string 47 UDSServer string 48 Cluster string 49 Dialer common.Dialer 50 IstioVersion string 51 Namespace string 52 DisableALPN bool 53 } 54 55 func (c Config) String() string { 56 var b strings.Builder 57 b.WriteString(fmt.Sprintf("Ports: %v\n", c.Ports)) 58 b.WriteString(fmt.Sprintf("BindIPPortsMap: %v\n", c.BindIPPortsMap)) 59 b.WriteString(fmt.Sprintf("BindLocalhostPortsMap: %v\n", c.BindLocalhostPortsMap)) 60 b.WriteString(fmt.Sprintf("Metrics: %v\n", c.Metrics)) 61 b.WriteString(fmt.Sprintf("TLSCert: %v\n", c.TLSCert)) 62 b.WriteString(fmt.Sprintf("TLSKey: %v\n", c.TLSKey)) 63 b.WriteString(fmt.Sprintf("Version: %v\n", c.Version)) 64 b.WriteString(fmt.Sprintf("UDSServer: %v\n", c.UDSServer)) 65 b.WriteString(fmt.Sprintf("Cluster: %v\n", c.Cluster)) 66 b.WriteString(fmt.Sprintf("IstioVersion: %v\n", c.IstioVersion)) 67 b.WriteString(fmt.Sprintf("Namespace: %v\n", c.Namespace)) 68 69 return b.String() 70 } 71 72 var ( 73 serverLog = log.RegisterScope("server", "echo server") 74 _ io.Closer = &Instance{} 75 ) 76 77 // Instance of the Echo server. 78 type Instance struct { 79 Config 80 81 endpoints []endpoint.Instance 82 metricsServer *http.Server 83 ready uint32 84 } 85 86 // New creates a new server instance. 87 func New(config Config) *Instance { 88 log.Infof("Creating Server with config:\n%s", config) 89 config.Dialer = config.Dialer.FillInDefaults() 90 91 return &Instance{ 92 Config: config, 93 } 94 } 95 96 // Start the server. 97 func (s *Instance) Start() (err error) { 98 defer func() { 99 if err != nil { 100 _ = s.Close() 101 } 102 }() 103 104 if err = s.validate(); err != nil { 105 return err 106 } 107 108 if s.Metrics > 0 { 109 go s.startMetricsServer() 110 } 111 s.endpoints = make([]endpoint.Instance, 0) 112 113 for _, p := range s.Ports { 114 ip, err := s.getListenerIP(p) 115 if err != nil { 116 return err 117 } 118 for _, ip := range getBindAddresses(ip) { 119 ep, err := s.newEndpoint(p, ip, "") 120 if err != nil { 121 return err 122 } 123 s.endpoints = append(s.endpoints, ep) 124 } 125 } 126 127 if len(s.UDSServer) > 0 { 128 ep, err := s.newEndpoint(nil, "", s.UDSServer) 129 if err != nil { 130 return err 131 } 132 s.endpoints = append(s.endpoints, ep) 133 } 134 135 return s.waitUntilReady() 136 } 137 138 func getBindAddresses(ip string) []string { 139 if ip != "" && ip != "localhost" { 140 return []string{ip} 141 } 142 // Binding to "localhost" will only bind to a single address (v4 or v6). We want both, so we need 143 // to be explicit 144 v4, v6 := false, false 145 // Obtain all the IPs from the node 146 ipAddrs, ok := network.GetPrivateIPs(context.Background()) 147 if !ok { 148 return []string{ip} 149 } 150 for _, ip := range ipAddrs { 151 addr, err := netip.ParseAddr(ip) 152 if err != nil { 153 // Should not happen 154 continue 155 } 156 if addr.Is4() { 157 v4 = true 158 } else { 159 v6 = true 160 } 161 } 162 addrs := []string{} 163 if v4 { 164 if ip == "localhost" { 165 addrs = append(addrs, "127.0.0.1") 166 } else { 167 addrs = append(addrs, "0.0.0.0") 168 } 169 } 170 if v6 { 171 if ip == "localhost" { 172 addrs = append(addrs, "::1") 173 } else { 174 addrs = append(addrs, "::") 175 } 176 } 177 return addrs 178 } 179 180 // Close implements the application.Application interface 181 func (s *Instance) Close() (err error) { 182 for _, s := range s.endpoints { 183 if s != nil { 184 err = multierror.Append(err, s.Close()) 185 } 186 } 187 return 188 } 189 190 func (s *Instance) getListenerIP(port *common.Port) (string, error) { 191 // Not configured on this port, set to empty which will lead to wildcard bind 192 // Not 0.0.0.0 in case we want IPv6 193 if port == nil { 194 return "", nil 195 } 196 if _, f := s.BindLocalhostPortsMap[port.Port]; f { 197 return "localhost", nil 198 } 199 if _, f := s.BindIPPortsMap[port.Port]; !f { 200 return "", nil 201 } 202 if ip, f := os.LookupEnv("INSTANCE_IP"); f { 203 return ip, nil 204 } 205 return "", fmt.Errorf("--bind-ip set but INSTANCE_IP undefined") 206 } 207 208 func (s *Instance) newEndpoint(port *common.Port, listenerIP string, udsServer string) (endpoint.Instance, error) { 209 return endpoint.New(endpoint.Config{ 210 Port: port, 211 UDSServer: udsServer, 212 IsServerReady: s.isReady, 213 Version: s.Version, 214 Cluster: s.Cluster, 215 TLSCert: s.TLSCert, 216 TLSKey: s.TLSKey, 217 Dialer: s.Dialer, 218 ListenerIP: listenerIP, 219 DisableALPN: s.DisableALPN, 220 IstioVersion: s.IstioVersion, 221 }) 222 } 223 224 func (s *Instance) isReady() bool { 225 return atomic.LoadUint32(&s.ready) == 1 226 } 227 228 func (s *Instance) waitUntilReady() error { 229 wg := &sync.WaitGroup{} 230 231 onEndpointReady := func() { 232 wg.Done() 233 } 234 235 // Start the servers, updating port numbers as necessary. 236 for _, ep := range s.endpoints { 237 wg.Add(1) 238 if err := ep.Start(onEndpointReady); err != nil { 239 return err 240 } 241 } 242 243 // Wait for all the servers to start. 244 wg.Wait() 245 246 // Indicate that the server is now ready. 247 atomic.StoreUint32(&s.ready, 1) 248 249 log.Info("Echo server is now ready") 250 return nil 251 } 252 253 func (s *Instance) validate() error { 254 for _, port := range s.Ports { 255 switch port.Protocol { 256 case protocol.TCP: 257 case protocol.UDP: 258 case protocol.HTTP: 259 case protocol.HTTPS: 260 case protocol.HTTP2: 261 case protocol.GRPC: 262 case protocol.HBONE: 263 default: 264 return fmt.Errorf("protocol %v not currently supported", port.Protocol) 265 } 266 } 267 return nil 268 } 269 270 func (s *Instance) startMetricsServer() { 271 mux := http.NewServeMux() 272 273 exporter, err := monitoring.RegisterPrometheusExporter(nil, nil) 274 if err != nil { 275 log.Errorf("could not set up prometheus exporter: %v", err) 276 return 277 } 278 mux.Handle("/metrics", LogRequests(exporter)) 279 s.metricsServer = &http.Server{ 280 Handler: mux, 281 } 282 if err := http.ListenAndServe(fmt.Sprintf(":%d", s.Metrics), mux); err != nil { 283 log.Errorf("metrics terminated with err: %v", err) 284 } 285 } 286 287 func LogRequests(next http.Handler) http.Handler { 288 return http.HandlerFunc( 289 func(w http.ResponseWriter, r *http.Request) { 290 serverLog.WithLabels( 291 "remoteAddr", r.RemoteAddr, "method", r.Method, "url", r.URL, "host", r.Host, "headers", r.Header, 292 ).Infof("Metrics Request") 293 next.ServeHTTP(w, r) 294 }, 295 ) 296 }