istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/proxystatus/proxystatus_test.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 proxystatus 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "net/http" 22 "strings" 23 "testing" 24 25 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 26 "github.com/spf13/cobra" 27 "google.golang.org/grpc" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/cli-runtime/pkg/resource" 31 "k8s.io/client-go/rest/fake" 32 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 33 cmdutil "k8s.io/kubectl/pkg/cmd/util" 34 35 "istio.io/api/label" 36 "istio.io/istio/istioctl/pkg/cli" 37 "istio.io/istio/istioctl/pkg/clioptions" 38 "istio.io/istio/istioctl/pkg/multixds" 39 "istio.io/istio/istioctl/pkg/xds" 40 "istio.io/istio/pkg/kube" 41 "istio.io/istio/pkg/test/util/assert" 42 ) 43 44 type execTestCase struct { 45 args []string 46 revision string 47 noIstiod bool 48 49 // Typically use one of the three 50 expectedOutput string // Expected constant output 51 expectedString string // String output is expected to contain 52 53 wantException bool 54 } 55 56 func TestProxyStatus(t *testing.T) { 57 cases := []execTestCase{ 58 { // case 0, with no Isitod instance 59 args: []string{}, 60 noIstiod: true, 61 expectedOutput: "Error: no running Istio pods in \"istio-system\"\n", 62 wantException: true, 63 }, 64 { // case 1, with Istiod instance 65 args: []string{}, 66 expectedString: "NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD", 67 }, 68 { // case 2: supplying nonexistent pod name should result in error with flag 69 args: strings.Split("deployment/random-gibberish", " "), 70 wantException: true, 71 }, 72 { // case 3: supplying nonexistent deployment name 73 args: strings.Split("deployment/random-gibberish.default", " "), 74 wantException: true, 75 }, 76 { // case 4: supplying nonexistent deployment name in nonexistent namespace 77 args: strings.Split("deployment/random-gibberish.bogus", " "), 78 wantException: true, 79 }, 80 { // case 5: supplying nonexistent pod name should result in error 81 args: strings.Split("random-gibberish-podname-61789237418234", " "), 82 wantException: true, 83 }, 84 { // case 6: new --revision argument 85 args: strings.Split("--revision canary", " "), 86 expectedString: "NAME CLUSTER CDS LDS EDS RDS ECDS ISTIOD", 87 revision: "canary", 88 }, 89 { // case 7: supplying type that doesn't select pods should fail 90 args: strings.Split("serviceaccount/sleep", " "), 91 wantException: true, 92 }, 93 } 94 multixds.GetXdsResponse = func(_ *discovery.DiscoveryRequest, _ string, _ string, _ clioptions.CentralControlPlaneOptions, _ []grpc.DialOption, 95 ) (*discovery.DiscoveryResponse, error) { 96 return &discovery.DiscoveryResponse{}, nil 97 } 98 t.Cleanup(func() { 99 multixds.GetXdsResponse = xds.GetXdsResponse 100 }) 101 102 for i, c := range cases { 103 t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.args, " ")), func(t *testing.T) { 104 ctx := cli.NewFakeContext(&cli.NewFakeContextOption{ 105 IstioNamespace: "istio-system", 106 }) 107 if !c.noIstiod { 108 client, err := ctx.CLIClientWithRevision(c.revision) 109 assert.NoError(t, err) 110 _, err = client.Kube().CoreV1().Pods("istio-system").Create(context.TODO(), &corev1.Pod{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: "istiod-test", 113 Namespace: "istio-system", 114 Labels: map[string]string{ 115 "app": "istiod", 116 label.IoIstioRev.Name: c.revision, 117 }, 118 }, 119 Status: corev1.PodStatus{ 120 Phase: corev1.PodRunning, 121 }, 122 }, metav1.CreateOptions{}) 123 assert.NoError(t, err) 124 } 125 verifyExecTestOutput(t, XdsStatusCommand(ctx), c) 126 }) 127 } 128 } 129 130 func verifyExecTestOutput(t *testing.T, cmd *cobra.Command, c execTestCase) { 131 t.Helper() 132 133 var out bytes.Buffer 134 cmd.SetArgs(c.args) 135 cmd.SilenceUsage = true 136 cmd.SetOut(&out) 137 cmd.SetErr(&out) 138 139 fErr := cmd.Execute() 140 output := out.String() 141 142 if c.expectedOutput != "" && c.expectedOutput != output { 143 t.Fatalf("Unexpected output for 'istioctl %s'\n got: %q\nwant: %q", strings.Join(c.args, " "), output, c.expectedOutput) 144 } 145 146 if c.expectedString != "" && !strings.Contains(output, c.expectedString) { 147 t.Fatalf("Output didn't match for '%s %s'\n got %v\nwant: %v", cmd.Name(), strings.Join(c.args, " "), output, c.expectedString) 148 } 149 150 if c.wantException { 151 if fErr == nil { 152 t.Fatalf("Wanted an exception for 'istioctl %s', didn't get one, output was %q", 153 strings.Join(c.args, " "), output) 154 } 155 } else { 156 if fErr != nil { 157 t.Fatalf("Unwanted exception for 'istioctl %s': %v", strings.Join(c.args, " "), fErr) 158 } 159 } 160 } 161 162 func init() { 163 cli.MakeKubeFactory = func(k kube.CLIClient) cmdutil.Factory { 164 tf := cmdtesting.NewTestFactory() 165 _, _, codec := cmdtesting.NewExternalScheme() 166 tf.UnstructuredClient = &fake.RESTClient{ 167 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 168 Resp: &http.Response{ 169 StatusCode: http.StatusOK, 170 Header: cmdtesting.DefaultHeader(), 171 Body: cmdtesting.ObjBody(codec, 172 cmdtesting.NewInternalType("", "", "foo")), 173 }, 174 } 175 return tf 176 } 177 }