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  }