Skip to content

Commit

Permalink
Issue #1579 TLSRoute Passthrough Conformance Test (normative) (#1587)
Browse files Browse the repository at this point in the history
* Issue #1579 TLSRoute Passthrough Conformance Test (normative) rebase

* Issue #1579 TLSRoute Passthrough - PR review update - rebase

* Issue #1579 TLSRoute Passthrough - PR review update - latest
  • Loading branch information
candita authored and shaneutt committed Feb 7, 2023
1 parent abec518 commit a201494
Show file tree
Hide file tree
Showing 12 changed files with 429 additions and 36 deletions.
62 changes: 62 additions & 0 deletions conformance/base/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,68 @@ spec:
cpu: 10m
---
apiVersion: v1
kind: Service
metadata:
name: tls-backend
namespace: gateway-conformance-infra
spec:
selector:
app: tls-backend
ports:
- protocol: TCP
port: 443
targetPort: 8443
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tls-backend
namespace: gateway-conformance-infra
labels:
app: tls-backend
spec:
replicas: 1
selector:
matchLabels:
app: tls-backend
template:
metadata:
labels:
app: tls-backend
spec:
containers:
- name: tls-backend
image: gcr.io/k8s-staging-ingressconformance/echoserver:v20221109-7ee2f3e
volumeMounts:
- name: secret-volume
mountPath: /etc/secret-volume
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: TLS_SERVER_CERT
value: /etc/secret-volume/crt
- name: TLS_SERVER_PRIVKEY
value: /etc/secret-volume/key
resources:
requests:
cpu: 10m
volumes:
- name: secret-volume
secret:
secretName: tls-passthrough-checks-certificate
items:
- key: tls.crt
path: crt
- key: tls.key
path: key
---
apiVersion: v1
kind: Namespace
metadata:
name: gateway-conformance-app-backend
Expand Down
5 changes: 3 additions & 2 deletions conformance/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ func TestConformance(t *testing.T) {
v1alpha2.AddToScheme(client.Scheme())
v1beta1.AddToScheme(client.Scheme())

t.Logf("Running conformance tests with %s GatewayClass", *flags.GatewayClassName)

supportedFeatures := parseSupportedFeatures(*flags.SupportedFeatures)
exemptFeatures := parseSupportedFeatures(*flags.ExemptFeatures)
for feature := range exemptFeatures {
supportedFeatures[feature] = false
}

t.Logf("Running conformance tests with %s GatewayClass\n cleanup: %t\n debug: %t\n supported features: [%v]\n exempt features: [%v]",
*flags.GatewayClassName, *flags.CleanupBaseResources, *flags.ShowDebug, *flags.SupportedFeatures, *flags.ExemptFeatures)

cSuite := suite.New(suite.Options{
Client: client,
GatewayClassName: *flags.GatewayClassName,
Expand Down
88 changes: 88 additions & 0 deletions conformance/tests/tlsroute-simple-same-namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package tests

import (
"context"
"fmt"
"testing"
"time"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"

"sigs.k8s.io/controller-runtime/pkg/client"

"sigs.k8s.io/gateway-api/apis/v1beta1"
"sigs.k8s.io/gateway-api/conformance/utils/http"
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
"sigs.k8s.io/gateway-api/conformance/utils/suite"
"sigs.k8s.io/gateway-api/conformance/utils/tls"
)

func init() {
ConformanceTests = append(ConformanceTests, TLSRouteSimpleSameNamespace)
}

var TLSRouteSimpleSameNamespace = suite.ConformanceTest{
ShortName: "TLSRouteSimpleSameNamespace",
Description: "A single TLSRoute in the gateway-conformance-infra namespace attaches to a Gateway in the same namespace",
Manifests: []string{"tests/tlsroute-simple-same-namespace.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
ns := v1beta1.Namespace("gateway-conformance-infra")
routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: string(ns)}
gwNN := types.NamespacedName{Name: "gateway-tlsroute", Namespace: string(ns)}
certNN := types.NamespacedName{Name: "tls-passthrough-checks-certificate", Namespace: string(ns)}

gwAddr, hostnames := kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
if len(hostnames) != 1 {
t.Fatalf("unexpected error in test configuration, found %d hostnames", len(hostnames))
}
serverStr := string(hostnames[0])

cPem, keyPem, err := GetTLSSecret(suite.Client, certNN)
if err != nil {
t.Fatalf("unexpected error finding TLS secret: %v", err)
}
t.Run("Simple TLS request matching TLSRoute should reach infra-backend", func(t *testing.T) {
tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, cPem, keyPem, serverStr,
http.ExpectedResponse{
Request: http.Request{Host: serverStr, Path: "/"},
Backend: "tls-backend",
Namespace: "gateway-conformance-infra",
})
})
},
}

// GetTLSSecret fetches the named Secret and converts both cert and key to []byte
func GetTLSSecret(client client.Client, secretName types.NamespacedName) ([]byte, []byte, error) {
var cert, key []byte

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

secret := &v1.Secret{}
err := client.Get(ctx, secretName, secret)
if err != nil {
return cert, key, fmt.Errorf("error fetching TLS Secret: %w", err)
}
cert = secret.Data["tls.crt"]
key = secret.Data["tls.key"]

return cert, key, nil
}
35 changes: 35 additions & 0 deletions conformance/tests/tlsroute-simple-same-namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: gateway-conformance-infra-test
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: gateway-tlsroute
namespace: gateway-conformance-infra
hostnames:
- abc.example.com
rules:
- backendRefs:
- name: tls-backend
port: 443
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: gateway-tlsroute
namespace: gateway-conformance-infra
spec:
gatewayClassName: "{GATEWAY_CLASS_NAME}"
listeners:
- name: https
port: 443
protocol: TLS
hostname: "*.example.com"
allowedRoutes:
namespaces:
from: Same
kinds:
- kind: TLSRoute
tls:
mode: Passthrough
16 changes: 11 additions & 5 deletions conformance/utils/config/timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ type TimeoutConfig struct {
// Max value for conformant implementation: None
HTTPRouteMustHaveCondition time.Duration

// HTTPRouteMustHaveParents represents the maximum time for an HTTPRoute to have parents in status that match the expected parents.
// RouteMustHaveParents represents the maximum time for an xRoute to have parents in status that match the expected parents.
// Max value for conformant implementation: None
HTTPRouteMustHaveParents time.Duration
RouteMustHaveParents time.Duration

// ManifestFetchTimeout represents the maximum time for getting content from a https:// URL.
// Max value for conformant implementation: None
Expand All @@ -70,6 +70,11 @@ type TimeoutConfig struct {
// RequestTimeout represents the maximum time for making an HTTP Request with the roundtripper.
// Max value for conformant implementation: None
RequestTimeout time.Duration

// RequiredConsecutiveSuccesses is the number of requests that must succeed in a row
// to consider a response "consistent" before making additional assertions on the response body.
// If this number is not reached within MaxTimeToConsistency, the test will fail.
RequiredConsecutiveSuccesses int
}

// DefaultTimeoutConfig populates a TimeoutConfig with the default values.
Expand All @@ -83,11 +88,12 @@ func DefaultTimeoutConfig() TimeoutConfig {
GWCMustBeAccepted: 180 * time.Second,
HTTPRouteMustNotHaveParents: 60 * time.Second,
HTTPRouteMustHaveCondition: 60 * time.Second,
HTTPRouteMustHaveParents: 60 * time.Second,
RouteMustHaveParents: 60 * time.Second,
ManifestFetchTimeout: 10 * time.Second,
MaxTimeToConsistency: 30 * time.Second,
NamespacesMustBeReady: 300 * time.Second,
RequestTimeout: 10 * time.Second,
RequiredConsecutiveSuccesses: 3,
}
}

Expand Down Expand Up @@ -117,8 +123,8 @@ func SetupTimeoutConfig(timeoutConfig *TimeoutConfig) {
if timeoutConfig.HTTPRouteMustHaveCondition == 0 {
timeoutConfig.HTTPRouteMustHaveCondition = defaultTimeoutConfig.HTTPRouteMustHaveCondition
}
if timeoutConfig.HTTPRouteMustHaveParents == 0 {
timeoutConfig.HTTPRouteMustHaveParents = defaultTimeoutConfig.HTTPRouteMustHaveParents
if timeoutConfig.RouteMustHaveParents == 0 {
timeoutConfig.RouteMustHaveParents = defaultTimeoutConfig.RouteMustHaveParents
}
if timeoutConfig.ManifestFetchTimeout == 0 {
timeoutConfig.ManifestFetchTimeout = defaultTimeoutConfig.ManifestFetchTimeout
Expand Down
32 changes: 17 additions & 15 deletions conformance/utils/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ type Response struct {
AbsentHeaders []string
}

// requiredConsecutiveSuccesses is the number of requests that must succeed in a row
// for MakeRequestAndExpectEventuallyConsistentResponse to consider the response "consistent"
// before making additional assertions on the response body. If this number is not reached within
// maxTimeToConsistency, the test will fail.
const requiredConsecutiveSuccesses = 3

// MakeRequestAndExpectEventuallyConsistentResponse makes a request with the given parameters,
// understanding that the request may fail for some amount of time.
//
Expand All @@ -96,6 +90,14 @@ const requiredConsecutiveSuccesses = 3
func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected ExpectedResponse) {
t.Helper()

req := MakeRequest(t, &expected, gwAddr, "HTTP", "http")

WaitForConsistentResponse(t, r, req, expected, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency)
}

func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, scheme string) roundtripper.Request {
t.Helper()

if expected.Request.Method == "" {
expected.Request.Method = "GET"
}
Expand All @@ -104,15 +106,15 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
expected.Response.StatusCode = 200
}

t.Logf("Making %s request to http://%s%s", expected.Request.Method, gwAddr, expected.Request.Path)
t.Logf("Making %s request to %s://%s%s", expected.Request.Method, scheme, gwAddr, expected.Request.Path)

path, query, _ := strings.Cut(expected.Request.Path, "?")

req := roundtripper.Request{
Method: expected.Request.Method,
Host: expected.Request.Host,
URL: url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query},
Protocol: "HTTP",
URL: url.URL{Scheme: scheme, Host: gwAddr, Path: path, RawQuery: query},
Protocol: protocol,
Headers: map[string][]string{},
UnfollowRedirect: expected.Request.UnfollowRedirect,
}
Expand All @@ -129,12 +131,12 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
}
req.Headers["X-Echo-Set-Header"] = []string{strings.Join(backendSetHeaders, ",")}

WaitForConsistentResponse(t, r, req, expected, requiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency)
return req
}

// awaitConvergence runs the given function until it returns 'true' `threshold` times in a row.
// AwaitConvergence runs the given function until it returns 'true' `threshold` times in a row.
// Each failed attempt has a 1s delay; successful attempts have no delay.
func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Duration, fn func(elapsed time.Duration) bool) {
func AwaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Duration, fn func(elapsed time.Duration) bool) {
successes := 0
attempts := 0
start := time.Now()
Expand Down Expand Up @@ -162,7 +164,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur
select {
// Capture the overall timeout
case <-to:
t.Fatalf("timeout while waiting after %d attempts, %d/%d sucessess", attempts, successes, threshold)
t.Fatalf("timeout while waiting after %d attempts, %d/%d successes", attempts, successes, threshold)
// And the per-try delay
case <-time.After(delay):
}
Expand All @@ -173,7 +175,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur
// the expected response consistently. The provided threshold determines how many times in
// a row this must occur to be considered "consistent".
func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected ExpectedResponse, threshold int, maxTimeToConsistency time.Duration) {
awaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
AwaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
cReq, cRes, err := r.CaptureRoundTrip(req)
if err != nil {
t.Logf("Request failed, not ready yet: %v (after %v)", err.Error(), elapsed)
Expand Down Expand Up @@ -312,7 +314,7 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
return nil
}

// Get User-defined test case name or generate from expected response to a given request.
// GetTestCaseName gets the user-defined test case name or generates one from expected response to a given request.
func (er *ExpectedResponse) GetTestCaseName(i int) string {

// If TestCase name is provided then use that or else generate one.
Expand Down
2 changes: 1 addition & 1 deletion conformance/utils/kubernetes/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func MustCreateSelfSignedCertSecret(t *testing.T, namespace, secretName string,
return newSecret
}

// generateRSACert generates a basic self signed certificate valir for a year
// generateRSACert generates a basic self signed certificate valid for a year
func generateRSACert(host string, keyOut, certOut io.Writer) error {
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
if err != nil {
Expand Down
Loading

0 comments on commit a201494

Please sign in to comment.