istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/echo/kube/sidecar.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 kube 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "strings" 22 "time" 23 24 admin "github.com/envoyproxy/go-control-plane/envoy/admin/v3" 25 "google.golang.org/protobuf/proto" 26 corev1 "k8s.io/api/core/v1" 27 28 // Import all XDS config types 29 _ "istio.io/istio/pkg/config/xds" 30 "istio.io/istio/pkg/test" 31 "istio.io/istio/pkg/test/framework/components/cluster" 32 "istio.io/istio/pkg/test/framework/components/echo" 33 "istio.io/istio/pkg/test/util/retry" 34 "istio.io/istio/pkg/util/protomarshal" 35 ) 36 37 const ( 38 proxyContainerName = "istio-proxy" 39 40 // DefaultTimeout the default timeout for the entire retry operation 41 defaultConfigTimeout = time.Second * 30 42 43 // DefaultDelay the default delay between successive retry attempts 44 defaultConfigDelay = time.Millisecond * 100 45 ) 46 47 var _ echo.Sidecar = &sidecar{} 48 49 type sidecar struct { 50 podNamespace string 51 podName string 52 cluster cluster.Cluster 53 } 54 55 func newSidecar(pod corev1.Pod, cluster cluster.Cluster) *sidecar { 56 sidecar := &sidecar{ 57 podNamespace: pod.Namespace, 58 podName: pod.Name, 59 cluster: cluster, 60 } 61 62 return sidecar 63 } 64 65 func (s *sidecar) Info() (*admin.ServerInfo, error) { 66 msg := &admin.ServerInfo{} 67 if err := s.adminRequest("server_info", msg); err != nil { 68 return nil, err 69 } 70 71 return msg, nil 72 } 73 74 func (s *sidecar) InfoOrFail(t test.Failer) *admin.ServerInfo { 75 t.Helper() 76 info, err := s.Info() 77 if err != nil { 78 t.Fatal(err) 79 } 80 return info 81 } 82 83 func (s *sidecar) Config() (*admin.ConfigDump, error) { 84 msg := &admin.ConfigDump{} 85 if err := s.adminRequest("config_dump", msg); err != nil { 86 return nil, err 87 } 88 89 return msg, nil 90 } 91 92 func (s *sidecar) ConfigOrFail(t test.Failer) *admin.ConfigDump { 93 t.Helper() 94 cfg, err := s.Config() 95 if err != nil { 96 t.Fatal(err) 97 } 98 return cfg 99 } 100 101 func (s *sidecar) WaitForConfig(accept func(*admin.ConfigDump) (bool, error), options ...retry.Option) error { 102 options = append([]retry.Option{retry.BackoffDelay(defaultConfigDelay), retry.Timeout(defaultConfigTimeout)}, options...) 103 104 var cfg *admin.ConfigDump 105 _, err := retry.UntilComplete(func() (result any, completed bool, err error) { 106 cfg, err = s.Config() 107 if err != nil { 108 if strings.Contains(err.Error(), "could not resolve Any message type") { 109 // Unable to parse an Any in the message, likely due to missing imports. 110 // This is not a recoverable error. 111 return nil, true, nil 112 } 113 if strings.Contains(err.Error(), `Any JSON doesn't have '@type'`) { 114 // Unable to parse an Any in the message, likely due to an older version. 115 // This is not a recoverable error. 116 return nil, true, nil 117 } 118 return nil, false, err 119 } 120 121 accepted, err := accept(cfg) 122 if err != nil { 123 // Accept returned an error - retry. 124 return nil, false, err 125 } 126 127 if accepted { 128 // The configuration was accepted. 129 return nil, true, nil 130 } 131 132 // The configuration was rejected, don't try again. 133 return nil, true, errors.New("envoy config rejected") 134 }, options...) 135 if err != nil { 136 configDumpStr := "nil" 137 if cfg != nil { 138 b, err := protomarshal.MarshalIndent(cfg, " ") 139 if err == nil { 140 configDumpStr = string(b) 141 } 142 } 143 144 return fmt.Errorf("failed waiting for Envoy configuration: %v. Last config_dump:\n%s", err, configDumpStr) 145 } 146 return nil 147 } 148 149 func (s *sidecar) WaitForConfigOrFail(t test.Failer, accept func(*admin.ConfigDump) (bool, error), options ...retry.Option) { 150 t.Helper() 151 if err := s.WaitForConfig(accept, options...); err != nil { 152 t.Fatal(err) 153 } 154 } 155 156 func (s *sidecar) Clusters() (*admin.Clusters, error) { 157 msg := &admin.Clusters{} 158 if err := s.adminRequest("clusters?format=json", msg); err != nil { 159 return nil, err 160 } 161 162 return msg, nil 163 } 164 165 func (s *sidecar) ClustersOrFail(t test.Failer) *admin.Clusters { 166 t.Helper() 167 clusters, err := s.Clusters() 168 if err != nil { 169 t.Fatal(err) 170 } 171 return clusters 172 } 173 174 func (s *sidecar) Listeners() (*admin.Listeners, error) { 175 msg := &admin.Listeners{} 176 if err := s.adminRequest("listeners?format=json", msg); err != nil { 177 return nil, err 178 } 179 180 return msg, nil 181 } 182 183 func (s *sidecar) ListenersOrFail(t test.Failer) *admin.Listeners { 184 t.Helper() 185 listeners, err := s.Listeners() 186 if err != nil { 187 t.Fatal(err) 188 } 189 return listeners 190 } 191 192 func (s *sidecar) adminRequest(path string, out proto.Message) error { 193 // Exec onto the pod and make a curl request to the admin port, writing 194 command := fmt.Sprintf("pilot-agent request GET %s", path) 195 stdout, stderr, err := s.cluster.PodExec(s.podName, s.podNamespace, proxyContainerName, command) 196 if err != nil { 197 return fmt.Errorf("failed exec on pod %s/%s: %v. Command: %s. Output:\n%s", 198 s.podNamespace, s.podName, err, command, stdout+stderr) 199 } 200 201 if err := protomarshal.UnmarshalAllowUnknown([]byte(stdout), out); err != nil { 202 return fmt.Errorf("failed parsing Envoy admin response from '/%s': %v\nResponse JSON: %s", path, err, stdout) 203 } 204 return nil 205 } 206 207 func (s *sidecar) Logs() (string, error) { 208 return s.cluster.PodLogs(context.TODO(), s.podName, s.podNamespace, proxyContainerName, false) 209 } 210 211 func (s *sidecar) LogsOrFail(t test.Failer) string { 212 t.Helper() 213 logs, err := s.Logs() 214 if err != nil { 215 t.Fatal(err) 216 } 217 return logs 218 }