Skip to content

Commit ca05ad6

Browse files
committed
Fix Gateway API combined sync coverage
- Fix Gateway API authz watches to use explicit unstructured GVKs - Watch ReferenceGrant as gateway.networking.k8s.io/v1beta1 - List HTTPRoute and TLSRoute via explicit unstructured list objects - Avoid scheme alias resolution selecting unsupported Gateway API versions - Remove unused Gateway watch and route-list helper dead code - Cover combined fromHost Gateway import with toHost tenant Gateway and HTTPRoute sync - Reuse Gateway API e2e client/bootstrap and kubectl apply helpers - Remove vacuous Gateway absence assertion from import-only suite - Move Gateway API enablement helpers to pkg/util/gatewayapi - Call Gateway API util enablement helpers directly from mappings registration Signed-off-by: Ryan Swanson <ryan.swanson@loft.sh>
1 parent 7e3140e commit ca05ad6

24 files changed

Lines changed: 1020 additions & 449 deletions

e2e-next/setup/csi.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,15 @@ func InstallCSIHostpath(kubeContext string) func(ctx context.Context) error {
133133
}
134134

135135
func kubectlApply(ctx context.Context, kubeContext string, files ...string) error {
136+
return kubectlApplyWithOptions(ctx, kubeContext, nil, files...)
137+
}
138+
139+
func kubectlApplyWithOptions(ctx context.Context, kubeContext string, options []string, files ...string) error {
136140
for _, f := range files {
137-
cmd := exec.CommandContext(ctx, "kubectl", "apply", "-f", f, "--context", kubeContext)
141+
args := []string{"apply"}
142+
args = append(args, options...)
143+
args = append(args, "-f", f, "--context", kubeContext)
144+
cmd := exec.CommandContext(ctx, "kubectl", args...)
138145
if out, err := cmd.CombinedOutput(); err != nil {
139146
if !strings.Contains(string(out), "already exists") {
140147
return fmt.Errorf("kubectl apply -f %s: %s: %w", f, string(out), err)

e2e-next/setup/gatewayapi.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package setup
33
import (
44
"context"
55
"fmt"
6-
"os/exec"
76
"path/filepath"
87
"runtime"
98

@@ -30,13 +29,9 @@ func GatewayAPIPreSetup() func(ctx context.Context) error {
3029
"pkg/mappings/resources/tlsroutes.crd.yaml",
3130
"pkg/mappings/resources/backendtlspolicies.crd.yaml",
3231
}
33-
for _, crd := range crds {
34-
abs := filepath.Join(repoRoot, crd)
35-
cmd := exec.CommandContext(ctx, "kubectl", "apply", "--server-side", "--force-conflicts", "--context", kubeContext, "-f", abs)
36-
if out, err := cmd.CombinedOutput(); err != nil {
37-
return fmt.Errorf("apply Gateway API CRD %s: %s: %w", crd, string(out), err)
38-
}
32+
for i, crd := range crds {
33+
crds[i] = filepath.Join(repoRoot, crd)
3934
}
40-
return nil
35+
return kubectlApplyWithOptions(ctx, kubeContext, []string{"--server-side", "--force-conflicts"}, crds...)
4136
}
4237
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Suite: gatewayapi-combined-vcluster
2+
// vCluster: fromHost Gateways and toHost Gateway API Gateways enabled together.
3+
// Run: just run-e2e 'pr && gatewayapi'
4+
package e2e_next
5+
6+
import (
7+
"context"
8+
_ "embed"
9+
10+
"github.com/loft-sh/e2e-framework/pkg/setup/cluster"
11+
"github.com/loft-sh/vcluster/e2e-next/clusters"
12+
"github.com/loft-sh/vcluster/e2e-next/labels"
13+
"github.com/loft-sh/vcluster/e2e-next/setup"
14+
"github.com/loft-sh/vcluster/e2e-next/setup/lazyvcluster"
15+
"github.com/loft-sh/vcluster/e2e-next/test_gatewayapi"
16+
. "github.com/onsi/ginkgo/v2"
17+
)
18+
19+
//go:embed vcluster-gatewayapi-combined.yaml
20+
var gatewayAPICombinedVClusterYAML string
21+
22+
const gatewayAPICombinedVClusterName = "gatewayapi-combined-vcluster"
23+
24+
func init() { suiteGatewayAPICombinedVCluster() }
25+
26+
func suiteGatewayAPICombinedVCluster() {
27+
// Ordered so the startup regression coverage runs against one vCluster with
28+
// both Gateway import and tenant Gateway sync enabled.
29+
Describe("gatewayapi-combined-vcluster", labels.PR, labels.GatewayAPI, labels.GatewayClasses, Ordered,
30+
cluster.Use(clusters.HostCluster),
31+
func() {
32+
BeforeAll(func(ctx context.Context) context.Context {
33+
return lazyvcluster.LazyVCluster(ctx,
34+
gatewayAPICombinedVClusterName,
35+
gatewayAPICombinedVClusterYAML,
36+
lazyvcluster.WithPreSetup(setup.GatewayAPIPreSetup()),
37+
)
38+
})
39+
40+
test_gatewayapi.GatewayAPICombinedSpec()
41+
},
42+
)
43+
}

e2e-next/suite_gatewayapi_import_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const gatewayAPIImportVClusterName = "gatewayapi-import-vcluster"
2525
func init() { suiteGatewayAPIImportVCluster() }
2626

2727
func suiteGatewayAPIImportVCluster() {
28+
// Ordered so all specs share one lazyvcluster bring-up; specs are independent.
2829
Describe("gatewayapi-import-vcluster", labels.PR, labels.GatewayAPI, labels.GatewayClasses, Ordered,
2930
cluster.Use(clusters.HostCluster),
3031
func() {

e2e-next/suite_gatewayapi_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const gatewayAPIVClusterName = "gatewayapi-vcluster"
2525
func init() { suiteGatewayAPIVCluster() }
2626

2727
func suiteGatewayAPIVCluster() {
28+
// Ordered so all specs share one lazyvcluster bring-up; specs are independent.
2829
Describe("gatewayapi-vcluster", labels.PR, labels.GatewayAPI, labels.GatewayClasses, Ordered,
2930
cluster.Use(clusters.HostCluster),
3031
func() {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package test_gatewayapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/loft-sh/e2e-framework/pkg/setup/cluster"
7+
"github.com/loft-sh/vcluster/e2e-next/constants"
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
13+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
14+
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
15+
gatewayv1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3"
16+
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
17+
)
18+
19+
type gatewayAPIClients struct {
20+
HostClient ctrlclient.Client
21+
VClusterClient ctrlclient.Client
22+
VClusterName string
23+
VClusterHostNS string
24+
}
25+
26+
func newGatewayAPIClients(ctx context.Context, installExtendedGatewaySchemes bool) gatewayAPIClients {
27+
GinkgoHelper()
28+
29+
scheme := runtime.NewScheme()
30+
Expect(corev1.AddToScheme(scheme)).To(Succeed())
31+
Expect(gatewayv1.Install(scheme)).To(Succeed())
32+
if installExtendedGatewaySchemes {
33+
Expect(gatewayv1alpha2.Install(scheme)).To(Succeed())
34+
Expect(gatewayv1alpha3.Install(scheme)).To(Succeed())
35+
Expect(gatewayv1beta1.Install(scheme)).To(Succeed())
36+
}
37+
38+
hostClient, err := ctrlclient.New(cluster.From(ctx, constants.GetHostClusterName()).KubernetesRestConfig(), ctrlclient.Options{Scheme: scheme})
39+
Expect(err).To(Succeed())
40+
vClusterClient, err := ctrlclient.New(cluster.CurrentClusterFrom(ctx).KubernetesRestConfig(), ctrlclient.Options{Scheme: scheme})
41+
Expect(err).To(Succeed())
42+
43+
vClusterName := cluster.CurrentClusterNameFrom(ctx)
44+
return gatewayAPIClients{
45+
HostClient: hostClient,
46+
VClusterClient: vClusterClient,
47+
VClusterName: vClusterName,
48+
VClusterHostNS: "vcluster-" + vClusterName,
49+
}
50+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package test_gatewayapi
2+
3+
import (
4+
"context"
5+
6+
"github.com/loft-sh/vcluster/e2e-next/constants"
7+
"github.com/loft-sh/vcluster/e2e-next/labels"
8+
"github.com/loft-sh/vcluster/pkg/controllers/resources/gateways"
9+
"github.com/loft-sh/vcluster/pkg/util/random"
10+
"github.com/loft-sh/vcluster/pkg/util/translate"
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
17+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
18+
)
19+
20+
const combinedClassSelectorValue = "gatewayapi-combined"
21+
22+
// GatewayAPICombinedSpec covers ENGNODE-556 / TC-33: importing host Gateways
23+
// and syncing tenant Gateways can be enabled together without startup loops.
24+
func GatewayAPICombinedSpec() {
25+
Describe("Gateway API combined import and tenant Gateway sync", labels.GatewayAPI, labels.GatewayClasses, func() {
26+
var (
27+
hostClient ctrlclient.Client
28+
vClusterClient ctrlclient.Client
29+
vClusterName string
30+
vClusterHostNS string
31+
)
32+
33+
gatewayNamespace := "gwapi-combined-host"
34+
BeforeEach(func(ctx context.Context) {
35+
clients := newGatewayAPIClients(ctx, false)
36+
hostClient = clients.HostClient
37+
vClusterClient = clients.VClusterClient
38+
vClusterName = clients.VClusterName
39+
vClusterHostNS = clients.VClusterHostNS
40+
41+
ensureHostNamespace(ctx, hostClient, gatewayNamespace)
42+
Eventually(func(g Gomega) {
43+
g.Expect(vClusterClient.List(ctx, &corev1.NamespaceList{})).To(Succeed())
44+
g.Expect(vClusterClient.List(ctx, &gatewayv1.GatewayClassList{})).To(Succeed())
45+
g.Expect(vClusterClient.List(ctx, &gatewayv1.GatewayList{}, ctrlclient.InNamespace("default"))).To(Succeed())
46+
g.Expect(vClusterClient.List(ctx, &gatewayv1.HTTPRouteList{}, ctrlclient.InNamespace("default"))).To(Succeed())
47+
}).WithPolling(constants.PollingInterval).WithTimeout(constants.PollingTimeout).Should(Succeed())
48+
})
49+
50+
It("starts with both Gateway import and tenant Gateway sync enabled", func(ctx context.Context) {
51+
suffix := random.String(6)
52+
class := createGatewayClass(ctx, hostClient, "gwc-combined-"+suffix, combinedClassSelectorValue, "combined class")
53+
hostGW := hostGateway(gatewayNamespace, "edge-"+suffix, class.Name)
54+
createHostGateway(ctx, hostClient, hostGW)
55+
56+
importedGatewayNamespace := "gwapi-combined-import"
57+
By("verifying the imported Gateway mirror appears after vCluster startup", func() {
58+
Eventually(func(g Gomega) {
59+
mirror := &gatewayv1.Gateway{}
60+
g.Expect(vClusterClient.Get(ctx, types.NamespacedName{Namespace: importedGatewayNamespace, Name: hostGW.Name}, mirror)).To(Succeed())
61+
g.Expect(mirror.Labels).To(HaveKeyWithValue(gateways.ImportedGatewayLabel, "true"))
62+
}).WithPolling(constants.PollingInterval).WithTimeout(constants.PollingTimeout).Should(Succeed())
63+
})
64+
65+
By("verifying the combined vCluster syncs tenant-authored Gateway API resources to host", func() {
66+
Eventually(func(g Gomega) {
67+
g.Expect(vClusterClient.Get(ctx, types.NamespacedName{Name: class.Name}, &gatewayv1.GatewayClass{})).To(Succeed())
68+
}).WithPolling(constants.PollingInterval).WithTimeout(constants.PollingTimeout).Should(Succeed())
69+
70+
tenantNS := createTenantNamespace(ctx, vClusterClient, "gwapi-combined-tenant-"+suffix)
71+
service := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "backend-" + suffix, Namespace: tenantNS.Name}, Spec: corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 80}}}}
72+
Expect(vClusterClient.Create(ctx, service)).To(Succeed())
73+
74+
tenantGW := tenantGateway(tenantNS.Name, "tenant-gw-"+suffix, class.Name)
75+
Expect(vClusterClient.Create(ctx, tenantGW)).To(Succeed())
76+
DeferCleanup(func(ctx context.Context) {
77+
Expect(ctrlclient.IgnoreNotFound(vClusterClient.Delete(ctx, tenantGW))).To(Succeed())
78+
})
79+
tenantRoute := tenantHTTPRoute(tenantNS.Name, "tenant-route-"+suffix, tenantGW.Name, service.Name)
80+
Expect(vClusterClient.Create(ctx, tenantRoute)).To(Succeed())
81+
DeferCleanup(func(ctx context.Context) {
82+
Expect(ctrlclient.IgnoreNotFound(vClusterClient.Delete(ctx, tenantRoute))).To(Succeed())
83+
})
84+
85+
hostGatewayName := translate.SafeConcatName(tenantGW.Name, "x", tenantNS.Name, "x", vClusterName)
86+
hostRouteName := translate.SafeConcatName(tenantRoute.Name, "x", tenantNS.Name, "x", vClusterName)
87+
Eventually(func(g Gomega) {
88+
gotGateway := &gatewayv1.Gateway{}
89+
g.Expect(hostClient.Get(ctx, types.NamespacedName{Namespace: vClusterHostNS, Name: hostGatewayName}, gotGateway)).To(Succeed())
90+
g.Expect(gotGateway.Spec.GatewayClassName).To(Equal(gatewayv1.ObjectName(class.Name)))
91+
92+
gotRoute := &gatewayv1.HTTPRoute{}
93+
g.Expect(hostClient.Get(ctx, types.NamespacedName{Namespace: vClusterHostNS, Name: hostRouteName}, gotRoute)).To(Succeed())
94+
g.Expect(gotRoute.Spec.ParentRefs).To(HaveLen(1))
95+
g.Expect(gotRoute.Spec.ParentRefs[0].Name).To(Equal(gatewayv1.ObjectName(hostGatewayName)))
96+
}).WithPolling(constants.PollingInterval).WithTimeout(constants.PollingTimeout).Should(Succeed())
97+
98+
Consistently(func(g Gomega) {
99+
g.Expect(vClusterClient.Get(ctx, ctrlclient.ObjectKeyFromObject(tenantGW), &gatewayv1.Gateway{})).To(Succeed())
100+
}).WithPolling(constants.PollingInterval).WithTimeout(constants.PollingTimeoutShort).Should(Succeed())
101+
})
102+
})
103+
})
104+
}

0 commit comments

Comments
 (0)