...

Source file src/github.com/linkerd/linkerd2/viz/cmd/stat_test.go

Documentation: github.com/linkerd/linkerd2/viz/cmd

     1  package cmd
     2  
     3  import (
     4  	"testing"
     5  
     6  	pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
     7  	"github.com/linkerd/linkerd2/pkg/k8s"
     8  	api "github.com/linkerd/linkerd2/viz/metrics-api"
     9  )
    10  
    11  type paramsExp struct {
    12  	counts  *api.PodCounts
    13  	options *statOptions
    14  	resNs   []string
    15  	file    string
    16  }
    17  
    18  func TestStat(t *testing.T) {
    19  	options := newStatOptions()
    20  	t.Run("Returns namespace stats", func(t *testing.T) {
    21  		testStatCall(paramsExp{
    22  			counts: &api.PodCounts{
    23  				MeshedPods:  1,
    24  				RunningPods: 2,
    25  				FailedPods:  0,
    26  			},
    27  			options: options,
    28  			resNs:   []string{"emojivoto1"},
    29  			file:    "stat_one_output.golden",
    30  		}, k8s.Namespace, t)
    31  	})
    32  
    33  	t.Run("Returns pod stats", func(t *testing.T) {
    34  		testStatCall(paramsExp{
    35  			counts: &api.PodCounts{
    36  				Status:      "Running",
    37  				MeshedPods:  1,
    38  				RunningPods: 1,
    39  				FailedPods:  0,
    40  			},
    41  			options: options,
    42  			resNs:   []string{"emojivoto1"},
    43  			file:    "stat_one_pod_output.golden",
    44  		}, k8s.Pod, t)
    45  	})
    46  
    47  	options.outputFormat = jsonOutput
    48  	t.Run("Returns namespace stats (json)", func(t *testing.T) {
    49  		testStatCall(paramsExp{
    50  			counts: &api.PodCounts{
    51  				MeshedPods:  1,
    52  				RunningPods: 2,
    53  				FailedPods:  0,
    54  			},
    55  			options: options,
    56  			resNs:   []string{"emojivoto1"},
    57  			file:    "stat_one_output_json.golden",
    58  		}, k8s.Namespace, t)
    59  	})
    60  
    61  	options = newStatOptions()
    62  	options.allNamespaces = true
    63  	t.Run("Returns all namespace stats", func(t *testing.T) {
    64  		testStatCall(paramsExp{
    65  			counts: &api.PodCounts{
    66  				MeshedPods:  1,
    67  				RunningPods: 2,
    68  				FailedPods:  0,
    69  			},
    70  			options: options,
    71  			resNs:   []string{"emojivoto1", "emojivoto2"},
    72  			file:    "stat_all_output.golden",
    73  		}, k8s.Namespace, t)
    74  	})
    75  
    76  	options.outputFormat = jsonOutput
    77  	t.Run("Returns all namespace stats (json)", func(t *testing.T) {
    78  		testStatCall(paramsExp{
    79  			counts: &api.PodCounts{
    80  				MeshedPods:  1,
    81  				RunningPods: 2,
    82  				FailedPods:  0,
    83  			},
    84  			options: options,
    85  			resNs:   []string{"emojivoto1", "emojivoto2"},
    86  			file:    "stat_all_output_json.golden",
    87  		}, k8s.Namespace, t)
    88  	})
    89  
    90  	options = newStatOptions()
    91  	options.outputFormat = "wide"
    92  	t.Run("Returns TCP stats", func(t *testing.T) {
    93  		testStatCall(paramsExp{
    94  			counts: &api.PodCounts{
    95  				MeshedPods:  1,
    96  				RunningPods: 2,
    97  				FailedPods:  0,
    98  			},
    99  			options: options,
   100  			resNs:   []string{"emojivoto1"},
   101  			file:    "stat_one_tcp_output.golden",
   102  		}, k8s.Namespace, t)
   103  	})
   104  
   105  	t.Run("Returns an error for named resource queries with the --all-namespaces flag", func(t *testing.T) {
   106  		options := newStatOptions()
   107  		options.allNamespaces = true
   108  		if options.namespace == "" {
   109  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   110  		}
   111  		args := []string{"po", "web"}
   112  		expectedError := "stats for a resource cannot be retrieved by name across all namespaces"
   113  
   114  		_, err := buildStatSummaryRequests(args, options)
   115  		if err == nil || err.Error() != expectedError {
   116  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   117  		}
   118  	})
   119  
   120  	t.Run("Rejects commands with both --to and --from flags", func(t *testing.T) {
   121  		options := newStatOptions()
   122  		if options.namespace == "" {
   123  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   124  		}
   125  		options.toResource = "deploy/foo"
   126  		options.fromResource = "deploy/bar"
   127  		args := []string{"ns", "test"}
   128  		expectedError := "--to and --from flags are mutually exclusive"
   129  
   130  		_, err := buildStatSummaryRequests(args, options)
   131  		if err == nil || err.Error() != expectedError {
   132  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   133  		}
   134  	})
   135  
   136  	t.Run("Rejects commands with both --to-namespace and --from-namespace flags", func(t *testing.T) {
   137  		options := newStatOptions()
   138  		if options.namespace == "" {
   139  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   140  		}
   141  		options.toNamespace = "foo"
   142  		options.fromNamespace = "bar"
   143  		args := []string{"po"}
   144  		expectedError := "--to-namespace and --from-namespace flags are mutually exclusive"
   145  
   146  		_, err := buildStatSummaryRequests(args, options)
   147  		if err == nil || err.Error() != expectedError {
   148  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   149  		}
   150  	})
   151  
   152  	t.Run("Rejects commands with both --all-namespaces and --namespace flags", func(t *testing.T) {
   153  		options := newStatOptions()
   154  		if options.namespace == "" {
   155  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   156  		}
   157  		options.allNamespaces = true
   158  		options.namespace = "ns"
   159  		args := []string{"po"}
   160  		expectedError := "--all-namespaces and --namespace flags are mutually exclusive"
   161  
   162  		_, err := buildStatSummaryRequests(args, options)
   163  		if err == nil || err.Error() != expectedError {
   164  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   165  		}
   166  	})
   167  
   168  	t.Run("Rejects --to-namespace flag when the target is a namespace", func(t *testing.T) {
   169  		options := newStatOptions()
   170  		if options.namespace == "" {
   171  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   172  		}
   173  		options.toNamespace = "bar"
   174  		args := []string{"ns", "foo"}
   175  		expectedError := "--to-namespace flag is incompatible with namespace resource type"
   176  
   177  		_, err := buildStatSummaryRequests(args, options)
   178  		if err == nil || err.Error() != expectedError {
   179  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   180  		}
   181  	})
   182  
   183  	t.Run("Rejects --from-namespace flag when the target is a namespace", func(t *testing.T) {
   184  		options := newStatOptions()
   185  		if options.namespace == "" {
   186  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   187  		}
   188  		options.fromNamespace = "foo"
   189  		args := []string{"ns/bar"}
   190  		expectedError := "--from-namespace flag is incompatible with namespace resource type"
   191  
   192  		_, err := buildStatSummaryRequests(args, options)
   193  		if err == nil || err.Error() != expectedError {
   194  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   195  		}
   196  	})
   197  
   198  	t.Run("Returns an error if --time-window is not more than 15s", func(t *testing.T) {
   199  		options := newStatOptions()
   200  		if options.namespace == "" {
   201  			options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   202  		}
   203  		options.timeWindow = "10s"
   204  		args := []string{"ns/bar"}
   205  		expectedError := "metrics time window needs to be at least 15s"
   206  
   207  		_, err := buildStatSummaryRequests(args, options)
   208  		if err == nil || err.Error() != expectedError {
   209  			t.Fatalf("Expected error [%s] instead got [%s]", expectedError, err)
   210  		}
   211  	})
   212  }
   213  
   214  func testStatCall(exp paramsExp, resourceType string, t *testing.T) {
   215  	mockClient := &api.MockAPIClient{}
   216  	response := api.GenStatSummaryResponse("emoji", resourceType, exp.resNs, exp.counts, true, true)
   217  
   218  	mockClient.StatSummaryResponseToReturn = response
   219  
   220  	if exp.options.namespace == "" {
   221  		exp.options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
   222  	}
   223  	reqs, err := buildStatSummaryRequests([]string{"ns"}, exp.options)
   224  	if err != nil {
   225  		t.Fatalf("Unexpected error: %v", err)
   226  	}
   227  
   228  	resp, err := requestStatsFromAPI(mockClient, reqs[0])
   229  	if err != nil {
   230  		t.Fatalf("Unexpected error: %v", err)
   231  	}
   232  
   233  	rows := respToRows(resp)
   234  	output := renderStatStats(rows, exp.options)
   235  
   236  	testDataDiffer.DiffTestdata(t, exp.file, output)
   237  }
   238  

View as plain text