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 }