istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/proxyconfig/proxyconfig_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 proxyconfig 16 17 import ( 18 "bytes" 19 "fmt" 20 "net/http" 21 "strings" 22 "testing" 23 24 "github.com/spf13/cobra" 25 "k8s.io/cli-runtime/pkg/resource" 26 "k8s.io/client-go/rest/fake" 27 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 28 cmdutil "k8s.io/kubectl/pkg/cmd/util" 29 30 "istio.io/istio/istioctl/pkg/cli" 31 "istio.io/istio/pilot/test/util" 32 "istio.io/istio/pkg/kube" 33 "istio.io/istio/pkg/test/util/assert" 34 ) 35 36 type execTestCase struct { 37 execClientConfig map[string][]byte 38 args []string 39 40 // Typically use one of the three 41 expectedOutput string // Expected constant output 42 expectedString string // String output is expected to contain 43 44 wantException bool 45 } 46 47 func TestProxyConfig(t *testing.T) { 48 loggingConfig := map[string][]byte{ 49 "details-v1-5b7f94f9bc-wp5tb": util.ReadFile(t, "../writer/envoy/logging/testdata/logging.txt"), 50 "httpbin-794b576b6c-qx6pf": []byte("{}"), 51 } 52 cases := []execTestCase{ 53 { 54 args: []string{}, 55 expectedString: "A group of commands used to retrieve information about", 56 }, 57 { // clusters invalid 58 args: strings.Split("clusters invalid", " "), 59 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 60 wantException: true, // "istioctl proxy-config clusters invalid" should fail 61 }, 62 { // listeners invalid 63 args: strings.Split("listeners invalid", " "), 64 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 65 wantException: true, // "istioctl proxy-config listeners invalid" should fail 66 }, 67 { // logging empty 68 args: strings.Split("log", " "), 69 expectedString: "Error: log requires pod name or --selector", 70 wantException: true, // "istioctl proxy-config logging empty" should fail 71 }, 72 { // logging invalid 73 args: strings.Split("log invalid", " "), 74 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 75 wantException: true, // "istioctl proxy-config logging invalid" should fail 76 }, 77 { // logging level invalid 78 execClientConfig: loggingConfig, 79 args: strings.Split("log details-v1-5b7f94f9bc-wp5tb --level xxx", " "), 80 expectedString: "unrecognized logging level: xxx", 81 wantException: true, 82 }, 83 { // logger name invalid 84 execClientConfig: loggingConfig, 85 args: strings.Split("log details-v1-5b7f94f9bc-wp5tb --level xxx:debug", " "), 86 expectedString: "unrecognized logger name: xxx", 87 wantException: true, 88 }, 89 { // logger name valid, but logging level invalid 90 execClientConfig: loggingConfig, 91 args: strings.Split("log details-v1-5b7f94f9bc-wp5tb --level http:yyy", " "), 92 expectedString: "unrecognized logging level: yyy", 93 wantException: true, 94 }, 95 { // both logger name and logging level invalid 96 execClientConfig: loggingConfig, 97 args: strings.Split("log details-v1-5b7f94f9bc-wp5tb --level xxx:yyy", " "), 98 expectedString: "unrecognized logger name: xxx", 99 wantException: true, 100 }, 101 { // routes invalid 102 args: strings.Split("routes invalid", " "), 103 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 104 wantException: true, // "istioctl proxy-config routes invalid" should fail 105 }, 106 { // bootstrap invalid 107 args: strings.Split("bootstrap invalid", " "), 108 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 109 wantException: true, // "istioctl proxy-config bootstrap invalid" should fail 110 }, 111 { // secret invalid 112 args: strings.Split("secret invalid", " "), 113 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 114 wantException: true, // "istioctl proxy-config secret invalid" should fail 115 }, 116 { // endpoint invalid 117 args: strings.Split("endpoint invalid", " "), 118 expectedString: "unable to retrieve Pod: pods \"invalid\" not found", 119 wantException: true, // "istioctl proxy-config endpoint invalid" should fail 120 }, 121 { // supplying nonexistent deployment name should result in error 122 args: strings.Split("clusters deployment/random-gibberish", " "), 123 expectedString: `"deployment/random-gibberish" does not refer to a pod`, 124 wantException: true, 125 }, 126 { // supplying nonexistent deployment name in nonexistent namespace 127 args: strings.Split("endpoint deployment/random-gibberish.bogus", " "), 128 expectedString: `"deployment/random-gibberish" does not refer to a pod`, 129 wantException: true, 130 }, 131 { // supplying type that doesn't select pods should fail 132 args: strings.Split("listeners serviceaccount/sleep", " "), 133 expectedString: `"serviceaccount/sleep" does not refer to a pod`, 134 wantException: true, 135 }, 136 { // supplying valid pod name retrieves Envoy config (fails because we don't check in Envoy config unit tests) 137 execClientConfig: loggingConfig, 138 args: strings.Split("clusters httpbin-794b576b6c-qx6pf", " "), 139 expectedString: `config dump has no configuration type`, 140 wantException: true, 141 }, 142 { // supplying valid pod name retrieves Envoy config (fails because we don't check in Envoy config unit tests) 143 execClientConfig: loggingConfig, 144 args: strings.Split("bootstrap httpbin-794b576b6c-qx6pf", " "), 145 expectedString: `config dump has no configuration type`, 146 wantException: true, 147 }, 148 { // supplying valid pod name retrieves Envoy config (fails because we don't check in Envoy config unit tests) 149 execClientConfig: loggingConfig, 150 args: strings.Split("endpoint httpbin-794b576b6c-qx6pf", " "), 151 expectedString: `ENDPOINT STATUS OUTLIER CHECK CLUSTER`, 152 wantException: false, 153 }, 154 { // supplying valid pod name retrieves Envoy config (fails because we don't check in Envoy config unit tests) 155 execClientConfig: loggingConfig, 156 args: strings.Split("listener httpbin-794b576b6c-qx6pf", " "), 157 expectedString: `config dump has no configuration type`, 158 wantException: true, 159 }, 160 { // supplying valid pod name retrieves Envoy config (fails because we don't check in Envoy config unit tests) 161 execClientConfig: loggingConfig, 162 args: strings.Split("route httpbin-794b576b6c-qx6pf", " "), 163 expectedString: `config dump has no configuration type`, 164 wantException: true, 165 }, 166 } 167 168 for i, c := range cases { 169 t.Run(fmt.Sprintf("case %d %s", i, strings.Join(c.args, " ")), func(t *testing.T) { 170 verifyExecTestOutput(t, ProxyConfig(cli.NewFakeContext(&cli.NewFakeContextOption{ 171 Results: c.execClientConfig, 172 Namespace: "default", 173 })), c) 174 }) 175 } 176 } 177 178 func verifyExecTestOutput(t *testing.T, cmd *cobra.Command, c execTestCase) { 179 t.Helper() 180 181 var out bytes.Buffer 182 cmd.SetArgs(c.args) 183 cmd.SilenceUsage = true 184 cmd.SetOut(&out) 185 cmd.SetErr(&out) 186 187 fErr := cmd.Execute() 188 output := out.String() 189 190 if c.expectedOutput != "" && c.expectedOutput != output { 191 t.Fatalf("Unexpected output for 'istioctl %s'\n got: %q\nwant: %q", strings.Join(c.args, " "), output, c.expectedOutput) 192 } 193 194 if c.expectedString != "" && !strings.Contains(output, c.expectedString) { 195 t.Fatalf("Output didn't match for '%s %s'\n got %v\nwant: %v", cmd.Name(), strings.Join(c.args, " "), output, c.expectedString) 196 } 197 198 if c.wantException { 199 if fErr == nil { 200 t.Fatalf("Wanted an exception for 'istioctl %s', didn't get one, output was %q", 201 strings.Join(c.args, " "), output) 202 } 203 } else { 204 if fErr != nil { 205 t.Fatalf("Unwanted exception for 'istioctl %s': %v", strings.Join(c.args, " "), fErr) 206 } 207 } 208 } 209 210 func TestPrintProxyConfigSummary(t *testing.T) { 211 cmd := ProxyConfig(cli.NewFakeContext(&cli.NewFakeContextOption{ 212 Namespace: "default", 213 })) 214 cmd.SetArgs([]string{ 215 "all", 216 "-f", "testdata/config_dump.json", 217 }) 218 out := bytes.Buffer{} 219 cmd.SetOut(&out) 220 assert.NoError(t, cmd.Execute()) 221 expected := util.ReadFile(t, "testdata/config_dump_summary.txt") 222 223 if err := assert.Compare(out.String(), string(expected)); err != nil { 224 t.Fatalf("Unexpected output for 'istioctl proxy-config all'\n got: %q\nwant: %q", out.String(), expected) 225 } 226 } 227 228 func init() { 229 cli.MakeKubeFactory = func(k kube.CLIClient) cmdutil.Factory { 230 tf := cmdtesting.NewTestFactory() 231 _, _, codec := cmdtesting.NewExternalScheme() 232 tf.UnstructuredClient = &fake.RESTClient{ 233 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 234 Resp: &http.Response{ 235 StatusCode: http.StatusOK, 236 Header: cmdtesting.DefaultHeader(), 237 Body: cmdtesting.ObjBody(codec, 238 cmdtesting.NewInternalType("", "", "foo")), 239 }, 240 } 241 return tf 242 } 243 }