...

Source file src/github.com/linkerd/linkerd2/test/integration/viz/trafficsplit/trafficsplit_test.go

Documentation: github.com/linkerd/linkerd2/test/integration/viz/trafficsplit

     1  package trafficsplit
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/linkerd/linkerd2/testutil"
    12  )
    13  
    14  var TestHelper *testutil.TestHelper
    15  
    16  func TestMain(m *testing.M) {
    17  	TestHelper = testutil.NewTestHelper()
    18  	// Block test execution until viz extension is running
    19  	TestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)
    20  	os.Exit(m.Run())
    21  }
    22  
    23  func parseStatRows(out string, expectedRowCount, expectedColumnCount int) ([]*testutil.RowStat, error) {
    24  	rows, err := testutil.CheckRowCount(out, expectedRowCount)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	var statRows []*testutil.RowStat
    30  
    31  	for _, row := range rows {
    32  		fields := strings.Fields(row)
    33  
    34  		if len(fields) != expectedColumnCount {
    35  			return nil, fmt.Errorf(
    36  				"Expected [%d] columns in stat output, got [%d]; full output:\n%s",
    37  				expectedColumnCount, len(fields), row)
    38  		}
    39  
    40  		row := &testutil.RowStat{
    41  			Name:               fields[0],
    42  			Meshed:             fields[1],
    43  			Success:            fields[2],
    44  			Rps:                fields[3],
    45  			P50Latency:         fields[4],
    46  			P95Latency:         fields[5],
    47  			P99Latency:         fields[6],
    48  			TCPOpenConnections: fields[7],
    49  		}
    50  
    51  		statRows = append(statRows, row)
    52  
    53  	}
    54  	return statRows, nil
    55  }
    56  
    57  func TestServiceProfileDstOverrides(t *testing.T) {
    58  	ctx := context.Background()
    59  	TestHelper.WithDataPlaneNamespace(ctx, "trafficsplit-test-sp", map[string]string{}, t, func(t *testing.T, prefixedNs string) {
    60  		// First, create a `ServiceProfile`.
    61  		initialSP := `---
    62  apiVersion: linkerd.io/v1alpha2
    63  kind: ServiceProfile
    64  metadata:
    65    name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
    66  spec:
    67    dstOverrides:
    68      - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
    69        weight: 1
    70      - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081
    71        weight: 0
    72  ...`
    73  		out, err := TestHelper.KubectlApply(initialSP, prefixedNs)
    74  		if err != nil {
    75  			testutil.AnnotatedFatalf(t, "Failed to apply ServiceProfile",
    76  				"Failed to apply ServiceProfile\n%s", out)
    77  		}
    78  
    79  		// Deploy an application that will route traffic to the `backend-svc`.
    80  		out, err = TestHelper.LinkerdRun("inject", "--manual",
    81  			"--proxy-log-level=linkerd=debug,linkerd_service_profiles::client=trace,info",
    82  			"testdata/applications-at-diff-ports.yaml")
    83  		if err != nil {
    84  			testutil.AnnotatedFatal(t, "'linkerd inject' command failed", err)
    85  		}
    86  		out, err = TestHelper.KubectlApply(out, prefixedNs)
    87  		if err != nil {
    88  			testutil.AnnotatedFatalf(t, "'kubectl apply' command failed",
    89  				"'kubectl apply' command failed\n%s", out)
    90  		}
    91  		// Wait for all of its pods to be ready.
    92  		for _, deploy := range []string{"backend", "failing", "slow-cooker"} {
    93  			if err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {
    94  				//nolint:errorlint
    95  				if rce, ok := err.(*testutil.RestartCountError); ok {
    96  					testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
    97  				} else {
    98  					testutil.AnnotatedError(t, "CheckPods timed-out", err)
    99  				}
   100  			}
   101  		}
   102  
   103  		t.Run("ensure traffic is sent to one backend only", func(t *testing.T) {
   104  			timeout := 40 * time.Second
   105  			expectedRows := []*testutil.RowStat{
   106  				{
   107  					Name:               "backend",
   108  					Meshed:             "1/1",
   109  					Success:            "100.00%",
   110  					TCPOpenConnections: "1",
   111  				},
   112  			}
   113  			err := testutil.RetryFor(timeout, func() error {
   114  				out, err := TestHelper.LinkerdRun("viz", "stat", "deploy", "--namespace", prefixedNs,
   115  					"--from", "deploy/slow-cooker", "-t", "30s")
   116  				if err != nil {
   117  					return err
   118  				}
   119  				rows, err := parseStatRows(out, 1, 8)
   120  				if err != nil {
   121  					return err
   122  				}
   123  				if err := validateRowStats(expectedRows, rows); err != nil {
   124  					return err
   125  				}
   126  				return nil
   127  			})
   128  			if err != nil {
   129  				testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out ensuring traffic is sent to one backend only (%s)", timeout), err)
   130  			}
   131  		})
   132  
   133  		t.Run("update traffic split resource with equal weights", func(t *testing.T) {
   134  			updatedSP := `---
   135  apiVersion: linkerd.io/v1alpha2
   136  kind: ServiceProfile
   137  metadata:
   138    name: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
   139  spec:
   140    dstOverrides:
   141      - authority: backend-svc.linkerd-trafficsplit-test-sp.svc.cluster.local
   142        weight: 1
   143      - authority: failing-svc.linkerd-trafficsplit-test-sp.svc.cluster.local:8081
   144        weight: 1
   145  ...`
   146  			out, err := TestHelper.KubectlApply(updatedSP, prefixedNs)
   147  			if err != nil {
   148  				testutil.AnnotatedFatalf(t, "failed to update ServiceProfile",
   149  					"failed to update ServiceProfile: %s\n %s", err, out)
   150  			}
   151  		})
   152  
   153  		t.Run("ensure traffic is sent to both backends", func(t *testing.T) {
   154  			timeout := 40 * time.Second
   155  			expectedRows := []*testutil.RowStat{
   156  				{
   157  					Name:               "backend",
   158  					Meshed:             "1/1",
   159  					Success:            "100.00%",
   160  					TCPOpenConnections: "1",
   161  				},
   162  				{
   163  					Name:               "failing",
   164  					Meshed:             "1/1",
   165  					Success:            "0.00%",
   166  					TCPOpenConnections: "1",
   167  				},
   168  			}
   169  			err := testutil.RetryFor(timeout, func() error {
   170  				out, err := TestHelper.LinkerdRun("viz", "stat", "deploy", "-n", prefixedNs,
   171  					"--from", "deploy/slow-cooker", "-t", "30s")
   172  				if err != nil {
   173  					return err
   174  				}
   175  				rows, err := parseStatRows(out, 2, 8)
   176  				if err != nil {
   177  					return err
   178  				}
   179  				if err := validateRowStats(expectedRows, rows); err != nil {
   180  					return err
   181  				}
   182  				return nil
   183  			})
   184  			if err != nil {
   185  				out, lerr := TestHelper.LinkerdRun("viz", "stat", "deploy", "-n", prefixedNs,
   186  					"--from", "deploy/slow-cooker", "-t", "30s")
   187  				if lerr == nil {
   188  					fmt.Fprintf(os.Stderr, "----------- linkerd viz stat\n")
   189  					fmt.Fprintf(os.Stderr, "%s", out)
   190  				}
   191  				out, lerr = TestHelper.Kubectl("", "logs", "--tail=1000", "-n", prefixedNs,
   192  					"-l", "app=slow-cooker", "-c", "linkerd-proxy")
   193  				if lerr != nil {
   194  					fmt.Fprintf(os.Stderr, "Failed to run kubectl logs: %s", lerr)
   195  				} else {
   196  					fmt.Fprintf(os.Stderr, "----------- kubectl logs\n")
   197  					fmt.Fprintf(os.Stderr, "%s", out)
   198  				}
   199  				testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out ensuring traffic is sent to both backends (%s)", timeout), err)
   200  			}
   201  		})
   202  	})
   203  }
   204  
   205  func validateRowStats(expectedRowStats, actualRowStats []*testutil.RowStat) error {
   206  	if len(expectedRowStats) != len(actualRowStats) {
   207  		return fmt.Errorf("Expected number of rows to be %d, but found %d", len(expectedRowStats), len(actualRowStats))
   208  	}
   209  	for i := 0; i < len(expectedRowStats); i++ {
   210  		err := compareRowStat(expectedRowStats[i], actualRowStats[i])
   211  		if err != nil {
   212  			return err
   213  		}
   214  	}
   215  	return nil
   216  }
   217  
   218  func compareRowStat(expectedRow, actualRow *testutil.RowStat) error {
   219  	if actualRow.Name != expectedRow.Name {
   220  		return fmt.Errorf("Expected name to be '%s', got '%s'",
   221  			expectedRow.Name, actualRow.Name)
   222  	}
   223  	if actualRow.Meshed != expectedRow.Meshed {
   224  		return fmt.Errorf("Expected meshed to be '%s', got '%s'",
   225  			expectedRow.Meshed, actualRow.Meshed)
   226  	}
   227  	if !strings.HasSuffix(actualRow.Rps, "rps") {
   228  		return fmt.Errorf("Unexpected rps for [%s], got [%s]",
   229  			actualRow.Name, actualRow.Rps)
   230  	}
   231  	if !strings.HasSuffix(actualRow.P50Latency, "ms") {
   232  		return fmt.Errorf("Unexpected p50 latency for [%s], got [%s]",
   233  			actualRow.Name, actualRow.P50Latency)
   234  	}
   235  	if !strings.HasSuffix(actualRow.P95Latency, "ms") {
   236  		return fmt.Errorf("Unexpected p95 latency for [%s], got [%s]",
   237  			actualRow.Name, actualRow.P95Latency)
   238  	}
   239  	if !strings.HasSuffix(actualRow.P99Latency, "ms") {
   240  		return fmt.Errorf("Unexpected p99 latency for [%s], got [%s]",
   241  			actualRow.Name, actualRow.P99Latency)
   242  	}
   243  	if actualRow.Success != expectedRow.Success {
   244  		return fmt.Errorf("Expected success to be '%s', got '%s'",
   245  			expectedRow.Success, actualRow.Success)
   246  	}
   247  	if actualRow.TCPOpenConnections != expectedRow.TCPOpenConnections {
   248  		return fmt.Errorf("Expected tcp to be '%s', got '%s'",
   249  			expectedRow.TCPOpenConnections, actualRow.TCPOpenConnections)
   250  	}
   251  	return nil
   252  }
   253  

View as plain text