2024-01-22 17:30:05 +03:00
package integration
import (
"context"
"fmt"
2024-04-17 16:22:04 +03:00
"io"
2024-06-22 06:46:03 +03:00
"io/fs"
2024-01-22 17:30:05 +03:00
"os"
"path/filepath"
2024-04-17 16:22:04 +03:00
"slices"
2024-09-03 13:10:04 +03:00
"strings"
2024-01-22 17:30:05 +03:00
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
2024-04-17 16:22:04 +03:00
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/k3s"
"github.com/testcontainers/testcontainers-go/network"
2024-01-22 17:30:05 +03:00
"github.com/traefik/traefik/v3/integration/try"
2024-09-17 17:40:04 +03:00
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway"
2024-06-22 06:46:03 +03:00
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2024-01-22 17:30:05 +03:00
"k8s.io/apimachinery/pkg/util/sets"
kclientset "k8s.io/client-go/kubernetes"
2024-09-26 10:12:04 +03:00
"k8s.io/client-go/rest"
2024-01-22 17:30:05 +03:00
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
2024-04-17 16:22:04 +03:00
klog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2024-01-22 17:30:05 +03:00
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
2024-06-22 06:46:03 +03:00
"sigs.k8s.io/gateway-api/conformance"
v1 "sigs.k8s.io/gateway-api/conformance/apis/v1"
2024-01-22 17:30:05 +03:00
"sigs.k8s.io/gateway-api/conformance/tests"
"sigs.k8s.io/gateway-api/conformance/utils/config"
ksuite "sigs.k8s.io/gateway-api/conformance/utils/suite"
2024-06-26 17:30:05 +03:00
"sigs.k8s.io/yaml"
2024-01-22 17:30:05 +03:00
)
2024-04-17 16:22:04 +03:00
const (
k3sImage = "docker.io/rancher/k3s:v1.29.3-k3s1"
traefikImage = "traefik/traefik:latest"
traefikDeployment = "deployments/traefik"
traefikNamespace = "traefik"
)
2024-01-22 17:30:05 +03:00
// K8sConformanceSuite tests suite.
2024-04-17 16:22:04 +03:00
type K8sConformanceSuite struct {
BaseSuite
k3sContainer * k3s . K3sContainer
kubeClient client . Client
2024-09-26 10:12:04 +03:00
restConfig * rest . Config
2024-04-17 16:22:04 +03:00
clientSet * kclientset . Clientset
}
2024-01-22 17:30:05 +03:00
func TestK8sConformanceSuite ( t * testing . T ) {
suite . Run ( t , new ( K8sConformanceSuite ) )
}
func ( s * K8sConformanceSuite ) SetupSuite ( ) {
2024-04-17 16:22:04 +03:00
if ! * k8sConformance {
s . T ( ) . Skip ( "Skip because it can take a long time to execute. To enable pass the `k8sConformance` flag." )
}
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
s . BaseSuite . SetupSuite ( )
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
// Avoid panic.
klog . SetLogger ( zap . New ( ) )
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
provider , err := testcontainers . ProviderDocker . GetProvider ( )
if err != nil {
s . T ( ) . Fatal ( err )
}
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
ctx := context . Background ( )
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
// Ensure image is available locally.
images , err := provider . ListImages ( ctx )
if err != nil {
s . T ( ) . Fatal ( err )
}
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
if ! slices . ContainsFunc ( images , func ( img testcontainers . ImageInfo ) bool {
return img . Name == traefikImage
} ) {
s . T ( ) . Fatal ( "Traefik image is not present" )
}
2024-01-22 17:30:05 +03:00
2024-08-12 12:34:04 +03:00
s . k3sContainer , err = k3s . Run ( ctx ,
k3sImage ,
2024-10-08 11:46:04 +03:00
k3s . WithManifest ( "./fixtures/k8s-conformance/00-experimental-v1.2.0.yml" ) ,
2024-04-17 16:22:04 +03:00
k3s . WithManifest ( "./fixtures/k8s-conformance/01-rbac.yml" ) ,
k3s . WithManifest ( "./fixtures/k8s-conformance/02-traefik.yml" ) ,
network . WithNetwork ( nil , s . network ) ,
)
if err != nil {
s . T ( ) . Fatal ( err )
2024-01-22 17:30:05 +03:00
}
2024-04-17 16:22:04 +03:00
if err = s . k3sContainer . LoadImages ( ctx , traefikImage ) ; err != nil {
s . T ( ) . Fatal ( err )
2024-01-22 17:30:05 +03:00
}
2024-04-17 16:22:04 +03:00
exitCode , _ , err := s . k3sContainer . Exec ( ctx , [ ] string { "kubectl" , "wait" , "-n" , traefikNamespace , traefikDeployment , "--for=condition=Available" , "--timeout=30s" } )
if err != nil || exitCode > 0 {
s . T ( ) . Fatalf ( "Traefik pod is not ready: %v" , err )
2024-01-22 17:30:05 +03:00
}
2024-04-17 16:22:04 +03:00
kubeConfigYaml , err := s . k3sContainer . GetKubeConfig ( ctx )
2024-01-22 17:30:05 +03:00
if err != nil {
s . T ( ) . Fatal ( err )
}
2024-09-26 10:12:04 +03:00
s . restConfig , err = clientcmd . RESTConfigFromKubeConfig ( kubeConfigYaml )
2024-04-17 16:22:04 +03:00
if err != nil {
s . T ( ) . Fatalf ( "Error loading Kubernetes config: %v" , err )
}
2024-09-26 10:12:04 +03:00
s . kubeClient , err = client . New ( s . restConfig , client . Options { } )
2024-01-22 17:30:05 +03:00
if err != nil {
s . T ( ) . Fatalf ( "Error initializing Kubernetes client: %v" , err )
}
2024-09-26 10:12:04 +03:00
s . clientSet , err = kclientset . NewForConfig ( s . restConfig )
2024-01-22 17:30:05 +03:00
if err != nil {
2024-04-17 16:22:04 +03:00
s . T ( ) . Fatalf ( "Error initializing Kubernetes REST client: %v" , err )
}
2024-06-22 06:46:03 +03:00
if err = gatev1alpha2 . Install ( s . kubeClient . Scheme ( ) ) ; err != nil {
2024-01-22 17:30:05 +03:00
s . T ( ) . Fatal ( err )
}
2024-06-22 06:46:03 +03:00
if err = gatev1beta1 . Install ( s . kubeClient . Scheme ( ) ) ; err != nil {
2024-04-17 16:22:04 +03:00
s . T ( ) . Fatal ( err )
}
2024-01-22 17:30:05 +03:00
2024-06-22 06:46:03 +03:00
if err = gatev1 . Install ( s . kubeClient . Scheme ( ) ) ; err != nil {
s . T ( ) . Fatal ( err )
}
if err = apiextensionsv1 . AddToScheme ( s . kubeClient . Scheme ( ) ) ; err != nil {
2024-04-17 16:22:04 +03:00
s . T ( ) . Fatal ( err )
}
}
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
func ( s * K8sConformanceSuite ) TearDownSuite ( ) {
ctx := context . Background ( )
if s . T ( ) . Failed ( ) || * showLog {
k3sLogs , err := s . k3sContainer . Logs ( ctx )
if err == nil {
if res , err := io . ReadAll ( k3sLogs ) ; err == nil {
s . T ( ) . Log ( string ( res ) )
}
}
2024-01-22 17:30:05 +03:00
2024-04-17 16:22:04 +03:00
exitCode , result , err := s . k3sContainer . Exec ( ctx , [ ] string { "kubectl" , "logs" , "-n" , traefikNamespace , traefikDeployment } )
if err == nil || exitCode == 0 {
if res , err := io . ReadAll ( result ) ; err == nil {
s . T ( ) . Log ( string ( res ) )
}
2024-01-22 17:30:05 +03:00
}
2024-04-17 16:22:04 +03:00
}
if err := s . k3sContainer . Terminate ( ctx ) ; err != nil {
s . T ( ) . Fatal ( err )
}
s . BaseSuite . TearDownSuite ( )
}
func ( s * K8sConformanceSuite ) TestK8sGatewayAPIConformance ( ) {
// Wait for traefik to start
k3sContainerIP , err := s . k3sContainer . ContainerIP ( context . Background ( ) )
require . NoError ( s . T ( ) , err )
2024-01-22 17:30:05 +03:00
2024-06-06 11:56:03 +03:00
err = try . GetRequest ( "http://" + k3sContainerIP + ":9000/api/entrypoints" , 10 * time . Second , try . BodyContains ( ` "name":"web" ` ) )
2024-01-22 17:30:05 +03:00
require . NoError ( s . T ( ) , err )
2024-06-22 06:46:03 +03:00
cSuite , err := ksuite . NewConformanceTestSuite ( ksuite . ConformanceOptions {
Client : s . kubeClient ,
Clientset : s . clientSet ,
GatewayClassName : "traefik" ,
Debug : true ,
CleanupBaseResources : true ,
2024-09-26 10:12:04 +03:00
RestConfig : s . restConfig ,
2024-06-22 06:46:03 +03:00
TimeoutConfig : config . DefaultTimeoutConfig ( ) ,
ManifestFS : [ ] fs . FS { & conformance . Manifests } ,
2024-01-22 17:30:05 +03:00
EnableAllSupportedFeatures : false ,
RunTest : * k8sConformanceRunTest ,
2024-06-22 06:46:03 +03:00
Implementation : v1 . Implementation {
2024-01-22 17:30:05 +03:00
Organization : "traefik" ,
Project : "traefik" ,
URL : "https://traefik.io/" ,
2024-08-30 18:14:03 +03:00
Version : * k8sConformanceTraefikVersion ,
2024-01-22 17:30:05 +03:00
Contact : [ ] string { "@traefik/maintainers" } ,
} ,
2024-09-03 13:10:04 +03:00
ConformanceProfiles : sets . New (
ksuite . GatewayHTTPConformanceProfileName ,
ksuite . GatewayGRPCConformanceProfileName ,
ksuite . GatewayTLSConformanceProfileName ,
) ,
2024-09-17 17:40:04 +03:00
SupportedFeatures : sets . New ( gateway . SupportedFeatures ( ) ... ) ,
2024-01-22 17:30:05 +03:00
} )
require . NoError ( s . T ( ) , err )
2024-06-22 06:46:03 +03:00
cSuite . Setup ( s . T ( ) , tests . ConformanceTests )
2024-01-22 17:30:05 +03:00
err = cSuite . Run ( s . T ( ) , tests . ConformanceTests )
require . NoError ( s . T ( ) , err )
report , err := cSuite . Report ( )
require . NoError ( s . T ( ) , err , "failed generating conformance report" )
2024-08-30 18:14:03 +03:00
// Ignore report date to avoid diff with CI job.
// However, we can track the date of the report thanks to the commit.
// TODO: to publish this report automatically, we have to figure out how to handle the date diff.
report . Date = "-"
2024-09-03 13:10:04 +03:00
// Ordering profile reports for the serialized report to be comparable.
slices . SortFunc ( report . ProfileReports , func ( a , b v1 . ProfileReport ) int {
return strings . Compare ( a . Name , b . Name )
} )
2024-01-22 17:30:05 +03:00
rawReport , err := yaml . Marshal ( report )
require . NoError ( s . T ( ) , err )
s . T ( ) . Logf ( "Conformance report:\n%s" , string ( rawReport ) )
2024-08-30 18:14:03 +03:00
require . NoError ( s . T ( ) , os . MkdirAll ( "./conformance-reports/" + report . GatewayAPIVersion , 0 o755 ) )
outFile := filepath . Join ( "conformance-reports/" + report . GatewayAPIVersion , fmt . Sprintf ( "%s-%s-%s-report.yaml" , report . GatewayAPIChannel , report . Version , report . Mode ) )
2024-01-22 17:30:05 +03:00
require . NoError ( s . T ( ) , os . WriteFile ( outFile , rawReport , 0 o600 ) )
s . T ( ) . Logf ( "Report written to: %s" , outFile )
}