istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/telemetry/tracing/tracing.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors. All Rights Reserved.
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package tracing
    19  
    20  import (
    21  	"fmt"
    22  	"strings"
    23  
    24  	"istio.io/istio/pkg/config/protocol"
    25  	"istio.io/istio/pkg/test/framework"
    26  	"istio.io/istio/pkg/test/framework/components/cluster"
    27  	"istio.io/istio/pkg/test/framework/components/echo"
    28  	"istio.io/istio/pkg/test/framework/components/echo/deployment"
    29  	"istio.io/istio/pkg/test/framework/components/echo/match"
    30  	"istio.io/istio/pkg/test/framework/components/istio"
    31  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    32  	"istio.io/istio/pkg/test/framework/components/namespace"
    33  	"istio.io/istio/pkg/test/framework/components/zipkin"
    34  	"istio.io/istio/pkg/test/framework/resource"
    35  )
    36  
    37  var (
    38  	client, server echo.Instances
    39  	ist            istio.Instance
    40  	ingInst        ingress.Instance
    41  	zipkinInst     zipkin.Instance
    42  	appNsInst      namespace.Instance
    43  )
    44  
    45  const (
    46  	TraceHeader = "x-client-trace-id"
    47  )
    48  
    49  func GetIstioInstance() *istio.Instance {
    50  	return &ist
    51  }
    52  
    53  // GetAppNamespace gets echo app namespace instance.
    54  func GetAppNamespace() namespace.Instance {
    55  	return appNsInst
    56  }
    57  
    58  func GetIngressInstance() ingress.Instance {
    59  	return ingInst
    60  }
    61  
    62  func GetZipkinInstance() zipkin.Instance {
    63  	return zipkinInst
    64  }
    65  
    66  func TestSetup(ctx resource.Context) (err error) {
    67  	appNsInst, err = namespace.New(ctx, namespace.Config{
    68  		Prefix: "echo",
    69  		Inject: true,
    70  	})
    71  	if err != nil {
    72  		return
    73  	}
    74  	builder := deployment.New(ctx)
    75  	for _, c := range ctx.Clusters() {
    76  		clName := c.Name()
    77  		builder = builder.
    78  			WithConfig(echo.Config{
    79  				Service:   fmt.Sprintf("client-%s", clName),
    80  				Namespace: appNsInst,
    81  				Cluster:   c,
    82  				Ports:     nil,
    83  				Subsets:   []echo.SubsetConfig{{}},
    84  			}).
    85  			WithConfig(echo.Config{
    86  				Service:   "server",
    87  				Namespace: appNsInst,
    88  				Cluster:   c,
    89  				Subsets:   []echo.SubsetConfig{{}},
    90  				Ports: []echo.Port{
    91  					{
    92  						Name:         "http",
    93  						Protocol:     protocol.HTTP,
    94  						WorkloadPort: 8090,
    95  					},
    96  					{
    97  						Name:     "tcp",
    98  						Protocol: protocol.TCP,
    99  						// We use a port > 1024 to not require root
   100  						WorkloadPort: 9000,
   101  					},
   102  				},
   103  			})
   104  	}
   105  	echos, err := builder.Build()
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	servicePrefix := func(prefix string) match.Matcher {
   111  		return func(i echo.Instance) bool {
   112  			return strings.HasPrefix(i.Config().Service, prefix)
   113  		}
   114  	}
   115  	client = servicePrefix("client").GetMatches(echos)
   116  	server = match.ServiceName(echo.NamespacedName{Name: "server", Namespace: appNsInst}).GetMatches(echos)
   117  	ingInst = ist.IngressFor(ctx.Clusters().Default())
   118  	addrs, _ := ingInst.HTTPAddresses()
   119  	zipkinInst, err = zipkin.New(ctx, zipkin.Config{Cluster: ctx.Clusters().Default(), IngressAddr: addrs[0]})
   120  	if err != nil {
   121  		return
   122  	}
   123  	return nil
   124  }
   125  
   126  func VerifyEchoTraces(t framework.TestContext, namespace, clName string, traces []zipkin.Trace) bool {
   127  	t.Helper()
   128  	wtr := WantTraceRoot(namespace, clName)
   129  	for _, trace := range traces {
   130  		// compare each candidate trace with the wanted trace
   131  		for _, s := range trace.Spans {
   132  			// find the root span of candidate trace and do recursive comparison
   133  			if s.ParentSpanID == "" && CompareTrace(t, s, wtr) {
   134  				return true
   135  			}
   136  		}
   137  	}
   138  
   139  	return false
   140  }
   141  
   142  func VerifyOtelEchoTraces(t framework.TestContext, namespace, clName string, traces []zipkin.Trace) bool {
   143  	t.Helper()
   144  	wtr := WantOtelTraceRoot(namespace, clName)
   145  	for _, trace := range traces {
   146  		// compare each candidate trace with the wanted trace
   147  		for _, s := range trace.Spans {
   148  			// find the root span of candidate trace and do recursive comparison
   149  			if s.ParentSpanID == "" && CompareTrace(t, s, wtr) {
   150  				return true
   151  			}
   152  		}
   153  	}
   154  
   155  	return false
   156  }
   157  
   158  func WantOtelTraceRoot(namespace, clName string) (root zipkin.Span) {
   159  	serverSpan := zipkin.Span{
   160  		Name:        fmt.Sprintf("server.%s.svc.cluster.local:80/*", namespace),
   161  		ServiceName: fmt.Sprintf("server.%s", namespace),
   162  	}
   163  
   164  	root = zipkin.Span{
   165  		Name:        fmt.Sprintf("server.%s.svc.cluster.local:80/*", namespace),
   166  		ServiceName: fmt.Sprintf("client-%s.%s", clName, namespace),
   167  		ChildSpans:  []*zipkin.Span{&serverSpan},
   168  	}
   169  	return
   170  }
   171  
   172  // compareTrace recursively compares the two given spans
   173  func CompareTrace(t framework.TestContext, got, want zipkin.Span) bool {
   174  	t.Helper()
   175  	if got.Name != want.Name || got.ServiceName != want.ServiceName {
   176  		t.Logf("got span %+v, want span %+v", got, want)
   177  		return false
   178  	}
   179  	if len(got.ChildSpans) < len(want.ChildSpans) {
   180  		t.Logf("got %d child spans from, want %d child spans, maybe trace has not be fully reported",
   181  			len(got.ChildSpans), len(want.ChildSpans))
   182  		return false
   183  	} else if len(got.ChildSpans) > len(want.ChildSpans) {
   184  		t.Logf("got %d child spans from, want %d child spans, maybe destination rule has not became effective",
   185  			len(got.ChildSpans), len(want.ChildSpans))
   186  		return false
   187  	}
   188  	for i := range got.ChildSpans {
   189  		if !CompareTrace(t, *got.ChildSpans[i], *want.ChildSpans[i]) {
   190  			return false
   191  		}
   192  	}
   193  	return true
   194  }
   195  
   196  // WantTraceRoot constructs the wanted trace and returns the root span of that trace
   197  func WantTraceRoot(namespace, clName string) (root zipkin.Span) {
   198  	serverSpan := zipkin.Span{
   199  		Name:        fmt.Sprintf("server.%s.svc.cluster.local:80/*", namespace),
   200  		ServiceName: fmt.Sprintf("server.%s", namespace),
   201  	}
   202  
   203  	root = zipkin.Span{
   204  		Name:        fmt.Sprintf("server.%s.svc.cluster.local:80/*", namespace),
   205  		ServiceName: fmt.Sprintf("client-%s.%s", clName, namespace),
   206  		ChildSpans:  []*zipkin.Span{&serverSpan},
   207  	}
   208  	return
   209  }
   210  
   211  // SendTraffic makes a client call to the "server" service on the http port.
   212  func SendTraffic(t framework.TestContext, headers map[string][]string, cl cluster.Cluster) error {
   213  	t.Helper()
   214  	t.Logf("Sending from %s...", cl.Name())
   215  	for _, cltInstance := range client {
   216  		if cltInstance.Config().Cluster != cl {
   217  			continue
   218  		}
   219  
   220  		_, err := cltInstance.Call(echo.CallOptions{
   221  			To: server,
   222  			Port: echo.Port{
   223  				Name: "http",
   224  			},
   225  			HTTP: echo.HTTP{
   226  				Headers: headers,
   227  			},
   228  			Retry: echo.Retry{
   229  				NoRetry: true,
   230  			},
   231  		})
   232  		if err != nil {
   233  			return err
   234  		}
   235  	}
   236  	return nil
   237  }