istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/test/xdstest/extract.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 xdstest
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"sort"
    21  
    22  	cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    23  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    24  	endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    25  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    26  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    27  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    28  	tcpproxy "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
    29  	tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    30  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    31  	"github.com/envoyproxy/go-control-plane/pkg/resource/v3"
    32  	"google.golang.org/protobuf/proto"
    33  	"google.golang.org/protobuf/types/known/anypb"
    34  
    35  	"istio.io/istio/pilot/pkg/model"
    36  	"istio.io/istio/pilot/pkg/networking/util"
    37  	"istio.io/istio/pilot/pkg/util/protoconv"
    38  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    39  	"istio.io/istio/pkg/maps"
    40  	"istio.io/istio/pkg/test"
    41  	"istio.io/istio/pkg/util/protomarshal"
    42  	"istio.io/istio/pkg/util/sets"
    43  	"istio.io/istio/pkg/wellknown"
    44  )
    45  
    46  func ExtractResource(res model.Resources) sets.String {
    47  	s := sets.New[string]()
    48  	for _, v := range res {
    49  		s.Insert(v.Name)
    50  	}
    51  	return s
    52  }
    53  
    54  func ExtractRoutesFromListeners(ll []*listener.Listener) []string {
    55  	routes := []string{}
    56  	for _, l := range ll {
    57  		for _, fc := range l.FilterChains {
    58  			for _, filter := range fc.Filters {
    59  				if filter.Name == wellknown.HTTPConnectionManager {
    60  					h := SilentlyUnmarshalAny[hcm.HttpConnectionManager](filter.GetTypedConfig())
    61  					switch r := h.GetRouteSpecifier().(type) {
    62  					case *hcm.HttpConnectionManager_Rds:
    63  						routes = append(routes, r.Rds.RouteConfigName)
    64  					}
    65  				}
    66  			}
    67  		}
    68  	}
    69  	return routes
    70  }
    71  
    72  func ExtractClusterSecretResources(t test.Failer, c *cluster.Cluster) []string {
    73  	resourceNames := sets.New[string]()
    74  	var sockets []*core.TransportSocket
    75  	if c.TransportSocket != nil {
    76  		sockets = append(sockets, c.TransportSocket)
    77  	}
    78  	for _, ts := range c.TransportSocketMatches {
    79  		sockets = append(sockets, ts.TransportSocket)
    80  	}
    81  	for _, s := range sockets {
    82  		if s.GetTypedConfig().TypeUrl != TypeName[*tls.UpstreamTlsContext]() {
    83  			continue
    84  		}
    85  		tl := UnmarshalAny[tls.UpstreamTlsContext](t, s.GetTypedConfig())
    86  		resourceNames.Insert(tl.GetCommonTlsContext().GetCombinedValidationContext().GetValidationContextSdsSecretConfig().GetName())
    87  		for _, s := range tl.GetCommonTlsContext().GetTlsCertificateSdsSecretConfigs() {
    88  			resourceNames.Insert(s.GetName())
    89  		}
    90  	}
    91  	return resourceNames.UnsortedList()
    92  }
    93  
    94  func ExtractListenerSecretResources(t test.Failer, l *listener.Listener) []string {
    95  	resourceNames := sets.New[string]()
    96  	var sockets []*core.TransportSocket
    97  	for _, fc := range l.GetFilterChains() {
    98  		if fc.GetTransportSocket() != nil {
    99  			sockets = append(sockets, fc.GetTransportSocket())
   100  		}
   101  	}
   102  	if ts := l.GetDefaultFilterChain().GetTransportSocket(); ts != nil {
   103  		sockets = append(sockets, ts)
   104  	}
   105  	for _, s := range sockets {
   106  		tl := UnmarshalAny[tls.DownstreamTlsContext](t, s.GetTypedConfig())
   107  		resourceNames.Insert(tl.GetCommonTlsContext().GetCombinedValidationContext().GetValidationContextSdsSecretConfig().GetName())
   108  		for _, s := range tl.GetCommonTlsContext().GetTlsCertificateSdsSecretConfigs() {
   109  			resourceNames.Insert(s.GetName())
   110  		}
   111  	}
   112  	return resourceNames.UnsortedList()
   113  }
   114  
   115  // ExtractSecretResources fetches all referenced SDS resource names from a list of clusters and listeners
   116  func ExtractSecretResources(t test.Failer, rs []*anypb.Any) []string {
   117  	resourceNames := sets.New[string]()
   118  	for _, r := range rs {
   119  		switch r.TypeUrl {
   120  		case v3.ClusterType:
   121  			c := UnmarshalAny[cluster.Cluster](t, r)
   122  			sockets := []*core.TransportSocket{}
   123  			if c.TransportSocket != nil {
   124  				sockets = append(sockets, c.TransportSocket)
   125  			}
   126  			for _, ts := range c.TransportSocketMatches {
   127  				sockets = append(sockets, ts.TransportSocket)
   128  			}
   129  			for _, s := range sockets {
   130  				tl := UnmarshalAny[tls.UpstreamTlsContext](t, s.GetTypedConfig())
   131  				resourceNames.Insert(tl.GetCommonTlsContext().GetCombinedValidationContext().GetValidationContextSdsSecretConfig().GetName())
   132  				for _, s := range tl.GetCommonTlsContext().GetTlsCertificateSdsSecretConfigs() {
   133  					resourceNames.Insert(s.GetName())
   134  				}
   135  			}
   136  		case v3.ListenerType:
   137  			l := UnmarshalAny[listener.Listener](t, r)
   138  			sockets := []*core.TransportSocket{}
   139  			for _, fc := range l.GetFilterChains() {
   140  				if fc.GetTransportSocket() != nil {
   141  					sockets = append(sockets, fc.GetTransportSocket())
   142  				}
   143  			}
   144  			if ts := l.GetDefaultFilterChain().GetTransportSocket(); ts != nil {
   145  				sockets = append(sockets, ts)
   146  			}
   147  			for _, s := range sockets {
   148  				tl := UnmarshalAny[tls.DownstreamTlsContext](t, s.GetTypedConfig())
   149  				resourceNames.Insert(tl.GetCommonTlsContext().GetCombinedValidationContext().GetValidationContextSdsSecretConfig().GetName())
   150  				for _, s := range tl.GetCommonTlsContext().GetTlsCertificateSdsSecretConfigs() {
   151  					resourceNames.Insert(s.GetName())
   152  				}
   153  			}
   154  		}
   155  	}
   156  	resourceNames.Delete("")
   157  	ls := resourceNames.UnsortedList()
   158  	sort.Sort(sort.Reverse(sort.StringSlice(ls)))
   159  	return ls
   160  }
   161  
   162  func ExtractListenerNames(ll []*listener.Listener) []string {
   163  	res := []string{}
   164  	for _, l := range ll {
   165  		res = append(res, l.Name)
   166  	}
   167  	return res
   168  }
   169  
   170  func SilentlyUnmarshalAny[T any](a *anypb.Any) *T {
   171  	dst := any(new(T)).(proto.Message)
   172  	if err := a.UnmarshalTo(dst); err != nil {
   173  		var z *T
   174  		return z
   175  	}
   176  	return any(dst).(*T)
   177  }
   178  
   179  func UnmarshalAny[T any](t test.Failer, a *anypb.Any) *T {
   180  	dst := any(new(T)).(proto.Message)
   181  	if err := a.UnmarshalTo(dst); err != nil {
   182  		t.Fatalf("failed to unmarshal to %T: %v", dst, err)
   183  	}
   184  	return any(dst).(*T)
   185  }
   186  
   187  func ExtractListener(name string, ll []*listener.Listener) *listener.Listener {
   188  	for _, l := range ll {
   189  		if l.Name == name {
   190  			return l
   191  		}
   192  	}
   193  	return nil
   194  }
   195  
   196  func ExtractVirtualHosts(rc *route.RouteConfiguration) map[string][]string {
   197  	res := map[string][]string{}
   198  	for _, vh := range rc.GetVirtualHosts() {
   199  		var dests []string
   200  		for _, r := range vh.Routes {
   201  			if dc := r.GetRoute().GetCluster(); dc != "" {
   202  				dests = append(dests, dc)
   203  			}
   204  		}
   205  		sort.Strings(dests)
   206  		for _, d := range vh.Domains {
   207  			res[d] = dests
   208  		}
   209  	}
   210  	return res
   211  }
   212  
   213  func ExtractRouteConfigurations(rc []*route.RouteConfiguration) map[string]*route.RouteConfiguration {
   214  	res := map[string]*route.RouteConfiguration{}
   215  	for _, l := range rc {
   216  		res[l.Name] = l
   217  	}
   218  	return res
   219  }
   220  
   221  func ExtractListenerFilters(l *listener.Listener) map[string]*listener.ListenerFilter {
   222  	res := map[string]*listener.ListenerFilter{}
   223  	for _, lf := range l.ListenerFilters {
   224  		res[lf.Name] = lf
   225  	}
   226  	return res
   227  }
   228  
   229  func ExtractFilterChain(name string, l *listener.Listener) *listener.FilterChain {
   230  	for _, f := range l.GetFilterChains() {
   231  		if f.GetName() == name {
   232  			return f
   233  		}
   234  	}
   235  	return nil
   236  }
   237  
   238  func ExtractFilterChainNames(l *listener.Listener) []string {
   239  	res := []string{}
   240  	for _, f := range l.GetFilterChains() {
   241  		res = append(res, f.GetName())
   242  	}
   243  	return res
   244  }
   245  
   246  func ExtractFilterNames(t test.Failer, fcs *listener.FilterChain) ([]string, []string) {
   247  	nwFilters := []string{}
   248  	httpFilters := []string{}
   249  	for _, fc := range fcs.Filters {
   250  		if fc.Name == wellknown.HTTPConnectionManager {
   251  			h := &hcm.HttpConnectionManager{}
   252  			if fc.GetTypedConfig() != nil {
   253  				if err := fc.GetTypedConfig().UnmarshalTo(h); err != nil {
   254  					t.Fatalf("failed to unmarshal hcm: %v", err)
   255  				}
   256  			}
   257  			for _, hf := range h.HttpFilters {
   258  				httpFilters = append(httpFilters, hf.Name)
   259  			}
   260  		}
   261  		nwFilters = append(nwFilters, fc.Name)
   262  	}
   263  	return nwFilters, httpFilters
   264  }
   265  
   266  func ExtractTCPProxy(t test.Failer, fcs *listener.FilterChain) *tcpproxy.TcpProxy {
   267  	for _, fc := range fcs.Filters {
   268  		if fc.Name == wellknown.TCPProxy {
   269  			tcpProxy := &tcpproxy.TcpProxy{}
   270  			if fc.GetTypedConfig() != nil {
   271  				if err := fc.GetTypedConfig().UnmarshalTo(tcpProxy); err != nil {
   272  					t.Fatalf("failed to unmarshal tcp proxy: %v", err)
   273  				}
   274  			}
   275  			return tcpProxy
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  func ExtractHTTPConnectionManager(t test.Failer, fcs *listener.FilterChain) *hcm.HttpConnectionManager {
   282  	for _, fc := range fcs.Filters {
   283  		if fc.Name == wellknown.HTTPConnectionManager {
   284  			h := &hcm.HttpConnectionManager{}
   285  			if fc.GetTypedConfig() != nil {
   286  				if err := fc.GetTypedConfig().UnmarshalTo(h); err != nil {
   287  					t.Fatalf("failed to unmarshal hcm: %v", err)
   288  				}
   289  			}
   290  			return h
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  func ExtractLocalityLbEndpoints(cla []*endpoint.ClusterLoadAssignment) map[string][]*endpoint.LocalityLbEndpoints {
   297  	got := map[string][]*endpoint.LocalityLbEndpoints{}
   298  	for _, cla := range cla {
   299  		if cla == nil {
   300  			continue
   301  		}
   302  		got[cla.ClusterName] = cla.Endpoints
   303  	}
   304  	return got
   305  }
   306  
   307  func ExtractLoadAssignments(cla []*endpoint.ClusterLoadAssignment) map[string][]string {
   308  	got := map[string][]string{}
   309  	for _, cla := range cla {
   310  		if cla == nil {
   311  			continue
   312  		}
   313  		got[cla.ClusterName] = append(got[cla.ClusterName], ExtractEndpoints(cla)...)
   314  	}
   315  	return got
   316  }
   317  
   318  // ExtractHealthEndpoints returns all health and unhealth endpoints
   319  func ExtractHealthEndpoints(cla *endpoint.ClusterLoadAssignment) ([]string, []string) {
   320  	if cla == nil {
   321  		return nil, nil
   322  	}
   323  	healthy := []string{}
   324  	unhealthy := []string{}
   325  	for _, ep := range cla.Endpoints {
   326  		for _, lb := range ep.LbEndpoints {
   327  			var addrString string
   328  			switch lb.GetEndpoint().GetAddress().Address.(type) {
   329  			case *core.Address_SocketAddress:
   330  				addrString = fmt.Sprintf("%s:%d",
   331  					lb.GetEndpoint().Address.GetSocketAddress().Address, lb.GetEndpoint().Address.GetSocketAddress().GetPortValue())
   332  			case *core.Address_Pipe:
   333  				addrString = lb.GetEndpoint().Address.GetPipe().Path
   334  			case *core.Address_EnvoyInternalAddress:
   335  				internalAddr := lb.GetEndpoint().Address.GetEnvoyInternalAddress().GetServerListenerName()
   336  				destinationAddr := lb.GetMetadata().GetFilterMetadata()[util.OriginalDstMetadataKey].GetFields()["local"].GetStringValue()
   337  				addrString = fmt.Sprintf("%s;%s", internalAddr, destinationAddr)
   338  			}
   339  			if lb.HealthStatus == core.HealthStatus_HEALTHY {
   340  				healthy = append(healthy, addrString)
   341  			} else {
   342  				unhealthy = append(unhealthy, addrString)
   343  			}
   344  		}
   345  	}
   346  	return healthy, unhealthy
   347  }
   348  
   349  // ExtractEndpoints returns all endpoints in the load assignment (including unhealthy endpoints)
   350  func ExtractEndpoints(cla *endpoint.ClusterLoadAssignment) []string {
   351  	h, uh := ExtractHealthEndpoints(cla)
   352  	h = append(h, uh...)
   353  	return h
   354  }
   355  
   356  func ExtractClusters(cc []*cluster.Cluster) map[string]*cluster.Cluster {
   357  	res := map[string]*cluster.Cluster{}
   358  	for _, c := range cc {
   359  		res[c.Name] = c
   360  	}
   361  	return res
   362  }
   363  
   364  func ExtractCluster(name string, cc []*cluster.Cluster) *cluster.Cluster {
   365  	return ExtractClusters(cc)[name]
   366  }
   367  
   368  func ExtractClusterEndpoints(clusters []*cluster.Cluster) map[string][]string {
   369  	cla := []*endpoint.ClusterLoadAssignment{}
   370  	for _, c := range clusters {
   371  		cla = append(cla, c.LoadAssignment)
   372  	}
   373  	return ExtractLoadAssignments(cla)
   374  }
   375  
   376  func ExtractEdsClusterNames(cl []*cluster.Cluster) []string {
   377  	res := []string{}
   378  	for _, c := range cl {
   379  		switch v := c.ClusterDiscoveryType.(type) {
   380  		case *cluster.Cluster_Type:
   381  			if v.Type != cluster.Cluster_EDS {
   382  				continue
   383  			}
   384  		default:
   385  			continue
   386  		}
   387  		res = append(res, c.Name)
   388  	}
   389  	return res
   390  }
   391  
   392  func ExtractTLSSecrets(t test.Failer, secrets []*anypb.Any) map[string]*tls.Secret {
   393  	res := map[string]*tls.Secret{}
   394  	for _, a := range secrets {
   395  		scrt := UnmarshalAny[tls.Secret](t, a)
   396  		res[scrt.Name] = scrt
   397  	}
   398  	return res
   399  }
   400  
   401  func UnmarshalRouteConfiguration(t test.Failer, resp []*anypb.Any) []*route.RouteConfiguration {
   402  	un := make([]*route.RouteConfiguration, 0, len(resp))
   403  	for _, r := range resp {
   404  		u := &route.RouteConfiguration{}
   405  		if err := r.UnmarshalTo(u); err != nil {
   406  			t.Fatal(err)
   407  		}
   408  		un = append(un, u)
   409  	}
   410  	return un
   411  }
   412  
   413  func UnmarshalClusterLoadAssignment(t test.Failer, resp []*anypb.Any) []*endpoint.ClusterLoadAssignment {
   414  	un := make([]*endpoint.ClusterLoadAssignment, 0, len(resp))
   415  	for _, r := range resp {
   416  		u := &endpoint.ClusterLoadAssignment{}
   417  		if err := r.UnmarshalTo(u); err != nil {
   418  			t.Fatal(err)
   419  		}
   420  		un = append(un, u)
   421  	}
   422  	return un
   423  }
   424  
   425  func FilterClusters(cl []*cluster.Cluster, f func(c *cluster.Cluster) bool) []*cluster.Cluster {
   426  	res := make([]*cluster.Cluster, 0, len(cl))
   427  	for _, c := range cl {
   428  		if f(c) {
   429  			res = append(res, c)
   430  		}
   431  	}
   432  	return res
   433  }
   434  
   435  func ToDiscoveryResponse[T proto.Message](p []T) *discovery.DiscoveryResponse {
   436  	resources := make([]*anypb.Any, 0, len(p))
   437  	for _, v := range p {
   438  		resources = append(resources, protoconv.MessageToAny(v))
   439  	}
   440  	return &discovery.DiscoveryResponse{
   441  		Resources: resources,
   442  		TypeUrl:   resources[0].TypeUrl,
   443  	}
   444  }
   445  
   446  // DumpList will dump a list of protos.
   447  func DumpList[T any](t test.Failer, protoList []T) []string {
   448  	res := []string{}
   449  	for _, i := range protoList {
   450  		p, ok := any(i).(proto.Message)
   451  		if !ok {
   452  			t.Fatalf("expected proto, got %T", i)
   453  		}
   454  		res = append(res, Dump(t, p))
   455  	}
   456  	return res
   457  }
   458  
   459  func Dump(t test.Failer, p proto.Message) string {
   460  	v := reflect.ValueOf(p)
   461  	if p == nil || (v.Kind() == reflect.Ptr && v.IsNil()) {
   462  		return "nil"
   463  	}
   464  	s, err := protomarshal.ToJSONWithIndent(p, "  ")
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	return s
   469  }
   470  
   471  func MapKeys[M ~map[string]V, V any](mp M) []string {
   472  	res := maps.Keys(mp)
   473  	sort.Strings(res)
   474  	return res
   475  }
   476  
   477  func TypeName[T proto.Message]() string {
   478  	ft := new(T)
   479  	return resource.APITypePrefix + string((*ft).ProtoReflect().Descriptor().FullName())
   480  }