...

Source file src/github.com/linkerd/linkerd2/test/integration/viz/policy/policy_test.go

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

     1  package policy
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strconv"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/linkerd/linkerd2/testutil"
    13  )
    14  
    15  var TestHelper *testutil.TestHelper
    16  
    17  func TestMain(m *testing.M) {
    18  	TestHelper = testutil.NewTestHelper()
    19  	// Block test execution until viz extension is running
    20  	TestHelper.WaitUntilDeployReady(testutil.LinkerdVizDeployReplicas)
    21  	os.Exit(m.Run())
    22  }
    23  
    24  //////////////////////
    25  /// TEST EXECUTION ///
    26  //////////////////////
    27  
    28  func TestPolicy(t *testing.T) {
    29  	ctx := context.Background()
    30  
    31  	// Test authorization stats
    32  	TestHelper.WithDataPlaneNamespace(ctx, "stat-authz-test", map[string]string{}, t, func(t *testing.T, prefixedNs string) {
    33  		emojivotoYaml, err := testutil.ReadFile("testdata/emojivoto.yaml")
    34  		if err != nil {
    35  			testutil.AnnotatedFatalf(t, "failed to read emojivoto yaml",
    36  				"failed to read emojivoto yaml\n%s\n", err)
    37  		}
    38  		emojivotoYaml = strings.ReplaceAll(emojivotoYaml, "___NS___", prefixedNs)
    39  		out, stderr, err := TestHelper.PipeToLinkerdRun(emojivotoYaml, "inject", "-")
    40  		if err != nil {
    41  			testutil.AnnotatedFatalf(t, "'linkerd inject' command failed",
    42  				"'linkerd inject' command failed\n%s\n%s", out, stderr)
    43  		}
    44  
    45  		out, err = TestHelper.KubectlApply(out, prefixedNs)
    46  		if err != nil {
    47  			testutil.AnnotatedFatalf(t, "failed to apply emojivoto resources",
    48  				"failed to apply emojivoto resources: %s\n %s", err, out)
    49  		}
    50  
    51  		emojivotoPolicy, err := testutil.ReadFile("testdata/emoji-policy.yaml")
    52  		if err != nil {
    53  			testutil.AnnotatedFatalf(t, "failed to read emoji-policy yaml",
    54  				"failed to read emoji-policy yaml\n%s\n", err)
    55  		}
    56  
    57  		out, err = TestHelper.KubectlApply(emojivotoPolicy, prefixedNs)
    58  		if err != nil {
    59  			testutil.AnnotatedFatalf(t, "failed to apply emojivoto policy resources",
    60  				"failed to apply emojivoto policy resources: %s\n %s", err, out)
    61  		}
    62  
    63  		// wait for deployments to start
    64  		for _, deploy := range []string{"web", "emoji", "vote-bot", "voting"} {
    65  			if err := TestHelper.CheckPods(ctx, prefixedNs, deploy, 1); err != nil {
    66  				//nolint:errorlint
    67  				if rce, ok := err.(*testutil.RestartCountError); ok {
    68  					testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
    69  				} else {
    70  					testutil.AnnotatedError(t, "CheckPods timed-out", err)
    71  				}
    72  			}
    73  		}
    74  
    75  		testCases := []struct {
    76  			args         []string
    77  			expectedRows []string
    78  			isServer     bool
    79  		}{
    80  			{
    81  				args: []string{"viz", "stat", "srv", "-n", prefixedNs},
    82  				expectedRows: []string{
    83  					"emoji-grpc",
    84  					"voting-grpc",
    85  					"web-http",
    86  				},
    87  				isServer: true,
    88  			},
    89  			{
    90  				args: []string{"viz", "stat", "srv/emoji-grpc", "-n", prefixedNs},
    91  				expectedRows: []string{
    92  					"emoji-grpc",
    93  				},
    94  				isServer: true,
    95  			},
    96  			{
    97  				args: []string{"viz", "stat", "saz", "-n", prefixedNs},
    98  				expectedRows: []string{
    99  					"emoji-grpc",
   100  					"voting-grpc",
   101  					"web-public",
   102  				},
   103  				isServer: false,
   104  			},
   105  			{
   106  				args: []string{"viz", "stat", "saz/emoji-grpc", "-n", prefixedNs},
   107  				expectedRows: []string{
   108  					"emoji-grpc",
   109  				},
   110  				isServer: false,
   111  			},
   112  		}
   113  
   114  		for _, tt := range testCases {
   115  			tt := tt // pin
   116  			timeout := 3 * time.Minute
   117  			t.Run("linkerd "+strings.Join(tt.args, " "), func(t *testing.T) {
   118  				err := testutil.RetryFor(timeout, func() error {
   119  					// Use a short time window so that transient errors at startup
   120  					// fall out of the window.
   121  					tt.args = append(tt.args, "-t", "30s")
   122  					out, err := TestHelper.LinkerdRun(tt.args...)
   123  					if err != nil {
   124  						testutil.AnnotatedFatalf(t, "unexpected stat error",
   125  							"unexpected stat error: %s\n%s", err, out)
   126  					}
   127  
   128  					var expectedColumnCount int
   129  					if tt.isServer {
   130  						expectedColumnCount = 8
   131  					} else {
   132  						expectedColumnCount = 6
   133  					}
   134  
   135  					rowStats, err := ParseAuthzRows(out, len(tt.expectedRows), expectedColumnCount, tt.isServer)
   136  					if err != nil {
   137  						return err
   138  					}
   139  
   140  					for _, name := range tt.expectedRows {
   141  						if err := validateAuthzRows(name, rowStats, tt.isServer); err != nil {
   142  							return err
   143  						}
   144  					}
   145  
   146  					return nil
   147  				})
   148  				if err != nil {
   149  					testutil.AnnotatedFatal(t, fmt.Sprintf("timed-out checking policy stats (%s)", timeout), err)
   150  				}
   151  			})
   152  		}
   153  	})
   154  }
   155  
   156  type noSuccess struct{ name string }
   157  
   158  func (e noSuccess) Error() string {
   159  	return fmt.Sprintf("no success rate reported for %s", e.name)
   160  }
   161  
   162  func validateAuthzRows(name string, rowStats map[string]*testutil.RowStat, isServer bool) error {
   163  	stat, ok := rowStats[name]
   164  	if !ok {
   165  		return fmt.Errorf("No stats found for [%s]", name)
   166  	}
   167  
   168  	// Check for suffix only, as the value will not be 100% always with
   169  	// the normal emojivoto sample
   170  	if stat.Success == "-" {
   171  		return noSuccess{name}
   172  	}
   173  	if !strings.HasSuffix(stat.Success, "%") {
   174  		return fmt.Errorf("Unexpected success rate for [%s], got [%s]",
   175  			name, stat.Success)
   176  	}
   177  
   178  	if isServer {
   179  		if !strings.HasSuffix(stat.UnauthorizedRPS, "rps") {
   180  			return fmt.Errorf("Unexpected Unauthorized RPS for [%s], got [%s]",
   181  				name, stat.UnauthorizedRPS)
   182  		}
   183  	}
   184  
   185  	if !strings.HasSuffix(stat.Rps, "rps") {
   186  		return fmt.Errorf("Unexpected rps for [%s], got [%s]",
   187  			name, stat.Rps)
   188  	}
   189  
   190  	if !strings.HasSuffix(stat.P50Latency, "ms") {
   191  		return fmt.Errorf("Unexpected p50 latency for [%s], got [%s]",
   192  			name, stat.P50Latency)
   193  	}
   194  
   195  	if !strings.HasSuffix(stat.P95Latency, "ms") {
   196  		return fmt.Errorf("Unexpected p95 latency for [%s], got [%s]",
   197  			name, stat.P95Latency)
   198  	}
   199  
   200  	if !strings.HasSuffix(stat.P99Latency, "ms") {
   201  		return fmt.Errorf("Unexpected p99 latency for [%s], got [%s]",
   202  			name, stat.P99Latency)
   203  	}
   204  
   205  	if isServer {
   206  		_, err := strconv.Atoi(stat.TCPOpenConnections)
   207  		if err != nil {
   208  			return fmt.Errorf("Error parsing number of TCP connections [%s]: %w", stat.TCPOpenConnections, err)
   209  		}
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  // ParseAuthzRows parses the output of linkerd stat on a policy resource
   216  func ParseAuthzRows(out string, expectedRowCount, expectedColumnCount int, isServer bool) (map[string]*testutil.RowStat, error) {
   217  	rows, err := testutil.CheckRowCount(out, expectedRowCount)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	rowStats := make(map[string]*testutil.RowStat)
   223  	for _, row := range rows {
   224  		fields := strings.Fields(row)
   225  
   226  		if len(fields) != expectedColumnCount {
   227  			return nil, fmt.Errorf(
   228  				"Expected [%d] columns in stat output, got [%d]; full output:\n%s",
   229  				expectedColumnCount, len(fields), row)
   230  		}
   231  
   232  		i := 0
   233  		rowStats[fields[0]] = &testutil.RowStat{
   234  			Name: fields[0],
   235  		}
   236  
   237  		if isServer {
   238  			rowStats[fields[0]].UnauthorizedRPS = fields[1+i]
   239  			rowStats[fields[0]].Success = fields[2+i]
   240  			rowStats[fields[0]].Rps = fields[3+i]
   241  			rowStats[fields[0]].P50Latency = fields[4+i]
   242  			rowStats[fields[0]].P95Latency = fields[5+i]
   243  			rowStats[fields[0]].P99Latency = fields[6+i]
   244  			rowStats[fields[0]].TCPOpenConnections = fields[7+i]
   245  		} else {
   246  			rowStats[fields[0]].Success = fields[1+i]
   247  			rowStats[fields[0]].Rps = fields[2+i]
   248  			rowStats[fields[0]].P50Latency = fields[3+i]
   249  			rowStats[fields[0]].P95Latency = fields[4+i]
   250  			rowStats[fields[0]].P99Latency = fields[5+i]
   251  		}
   252  
   253  	}
   254  
   255  	return rowStats, nil
   256  }
   257  

View as plain text