...

Source file src/sigs.k8s.io/gateway-api/conformance/utils/echo/pod.go

Documentation: sigs.k8s.io/gateway-api/conformance/utils/echo

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package echo
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	klabels "k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/client-go/kubernetes/scheme"
    30  	"k8s.io/client-go/rest"
    31  	"k8s.io/client-go/tools/remotecommand"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  
    34  	"sigs.k8s.io/gateway-api/conformance/utils/config"
    35  	"sigs.k8s.io/gateway-api/conformance/utils/http"
    36  	"sigs.k8s.io/gateway-api/conformance/utils/suite"
    37  )
    38  
    39  // MeshPod represents a connection to a specific pod running in the mesh.
    40  // This can be used to trigger requests *from* that pod.
    41  type MeshPod struct {
    42  	Name      string
    43  	Namespace string
    44  	Address   string
    45  	rc        rest.Interface
    46  	rcfg      *rest.Config
    47  }
    48  
    49  type MeshApplication string
    50  
    51  const (
    52  	MeshAppEchoV1 MeshApplication = "app=echo,version=v1"
    53  	MeshAppEchoV2 MeshApplication = "app=echo,version=v2"
    54  )
    55  
    56  func (m *MeshPod) MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, exp http.ExpectedResponse, timeoutConfig config.TimeoutConfig) {
    57  	t.Helper()
    58  
    59  	http.AwaitConvergence(t, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency, func(elapsed time.Duration) bool {
    60  		req := makeRequest(t, exp.Request)
    61  
    62  		resp, err := m.request(req)
    63  		if err != nil {
    64  			t.Logf("Request %v failed, not ready yet: %v (after %v)", req, err.Error(), elapsed)
    65  			return false
    66  		}
    67  		t.Logf("Got resp %v", resp)
    68  		if err := compareRequest(exp, resp); err != nil {
    69  			t.Logf("Response expectation failed for request: %v  not ready yet: %v (after %v)", req, err, elapsed)
    70  			return false
    71  		}
    72  		return true
    73  	})
    74  
    75  	t.Logf("Request passed")
    76  }
    77  
    78  func makeRequest(t *testing.T, r http.Request) []string {
    79  	protocol := strings.ToLower(r.Protocol)
    80  	if protocol == "" {
    81  		protocol = "http"
    82  	}
    83  	host := http.CalculateHost(t, r.Host, protocol)
    84  	args := []string{"client", fmt.Sprintf("%s://%s%s", protocol, host, r.Path)}
    85  	if r.Method != "" {
    86  		args = append(args, "--method="+r.Method)
    87  	}
    88  	for k, v := range r.Headers {
    89  		args = append(args, "-H", fmt.Sprintf("%v: %v", k, v))
    90  	}
    91  	return args
    92  }
    93  
    94  func compareRequest(exp http.ExpectedResponse, resp Response) error {
    95  	want := exp.Response
    96  	if fmt.Sprint(want.StatusCode) != resp.Code {
    97  		return fmt.Errorf("wanted status code %v, got %v", want.StatusCode, resp.Code)
    98  	}
    99  	for _, name := range want.AbsentHeaders {
   100  		if v := resp.ResponseHeaders.Values(name); len(v) != 0 {
   101  			return fmt.Errorf("expected no header %q, got %v", name, v)
   102  		}
   103  	}
   104  	for k, v := range want.Headers {
   105  		if got := resp.ResponseHeaders.Get(k); got != v {
   106  			return fmt.Errorf("expected header %v=%v, got %v", k, v, got)
   107  		}
   108  	}
   109  	if !strings.HasPrefix(resp.Hostname, exp.Backend) {
   110  		return fmt.Errorf("expected pod name to start with %s, got %s", exp.Backend, resp.Hostname)
   111  	}
   112  	return nil
   113  }
   114  
   115  func (m *MeshPod) request(args []string) (Response, error) {
   116  	container := "echo"
   117  
   118  	req := m.rc.Post().
   119  		Resource("pods").
   120  		Name(m.Name).
   121  		Namespace(m.Namespace).
   122  		SubResource("exec").
   123  		Param("container", container).
   124  		VersionedParams(&v1.PodExecOptions{
   125  			Container: container,
   126  			Command:   args,
   127  			Stdin:     false,
   128  			Stdout:    true,
   129  			Stderr:    true,
   130  			TTY:       false,
   131  		}, scheme.ParameterCodec)
   132  
   133  	exec, err := remotecommand.NewSPDYExecutor(m.rcfg, "POST", req.URL())
   134  	if err != nil {
   135  		return Response{}, err
   136  	}
   137  
   138  	var stdoutBuf, stderrBuf bytes.Buffer
   139  	err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
   140  		Stdin:  nil,
   141  		Stdout: &stdoutBuf,
   142  		Stderr: &stderrBuf,
   143  		Tty:    false,
   144  	})
   145  	if err != nil {
   146  		return Response{}, err
   147  	}
   148  
   149  	return ParseResponse(stdoutBuf.String()), nil
   150  }
   151  
   152  func ConnectToApp(t *testing.T, s *suite.ConformanceTestSuite, app MeshApplication) MeshPod {
   153  	return ConnectToAppInNamespace(t, s, app, "gateway-conformance-mesh")
   154  }
   155  
   156  func ConnectToAppInNamespace(t *testing.T, s *suite.ConformanceTestSuite, app MeshApplication, ns string) MeshPod {
   157  	lbls, _ := klabels.Parse(string(app))
   158  
   159  	podsList := v1.PodList{}
   160  	err := s.Client.List(context.Background(), &podsList, client.InNamespace(ns), client.MatchingLabelsSelector{Selector: lbls})
   161  	if err != nil {
   162  		t.Fatalf("failed to query pods in app %v", app)
   163  	}
   164  	if len(podsList.Items) == 0 {
   165  		t.Fatalf("no pods found in app %v", app)
   166  	}
   167  	pod := podsList.Items[0]
   168  	podName := pod.Name
   169  	podNamespace := pod.Namespace
   170  
   171  	return MeshPod{
   172  		Name:      podName,
   173  		Namespace: podNamespace,
   174  		Address:   pod.Status.PodIP,
   175  		rc:        s.Clientset.CoreV1().RESTClient(),
   176  		rcfg:      s.RestConfig,
   177  	}
   178  }
   179  

View as plain text