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  }