Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pkg/controller/gatewayapi/gatewayapi_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,19 @@ func (r *ReconcileGatewayAPI) Reconcile(ctx context.Context, request reconcile.R
reqLogger.Info("Could not render all optional GatewayAPI CRDs", "err", err)
}

// Render the tigera-gateway Namespace early — before any of the steps below that may
// early-return (e.g. EnvoyProxyRef resolution). The namespace is part of the operator's
// contract for any GatewayAPI CR, so it must always exist; otherwise users referencing
// custom EnvoyProxy resources in tigera-gateway on a fresh install hit a deadlock where
// reconcile fails on the missing EnvoyProxy and never reaches the non-CRD render that
// would have created the namespace.
namespaceComponent := gatewayapi.GatewayAPINamespaceComponent(installationSpec)
err = r.newComponentHandler(log, r.client, r.scheme, gatewayAPI).CreateOrUpdateOrDelete(ctx, namespaceComponent, nil)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceCreateError, "Error rendering tigera-gateway namespace", err, log)
return reconcile.Result{}, err
}

pullSecrets, err := utils.GetInstallationPullSecrets(installationSpec, r.client)
if err != nil {
r.status.SetDegraded(operatorv1.ResourceReadError, "Error retrieving pull secrets", err, reqLogger)
Expand Down
62 changes: 57 additions & 5 deletions pkg/controller/gatewayapi/gatewayapi_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,13 @@ var _ = Describe("Gateway API controller tests", func() {
Expect(err).ShouldNot(HaveOccurred())

By("checking the component handlers")
Expect(fakeComponentHandlers).To(HaveLen(2))
Expect(fakeComponentHandlers).To(HaveLen(3))
Expect(fakeComponentHandlers[0].createOnly).To(BeTrue())
Expect(fakeComponentHandlers[1].createOnly).To(BeFalse())
Expect(fakeComponentHandlers[2].createOnly).To(BeFalse())

By("checking that the custom EnvoyGateway was passed through")
gatewayAPIImplementationConfig := fakeComponentHandlers[1].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
gatewayAPIImplementationConfig := fakeComponentHandlers[2].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
Expect(gatewayAPIImplementationConfig.CustomEnvoyGateway).NotTo(BeNil())
Expect(*gatewayAPIImplementationConfig.CustomEnvoyGateway).To(Equal(*envoyGateway))
})
Expand Down Expand Up @@ -300,7 +301,7 @@ var _ = Describe("Gateway API controller tests", func() {
Expect(err).ShouldNot(HaveOccurred())

By("checking that the custom EnvoyGateway was passed through")
gatewayAPIImplementationConfig := fakeComponentHandlers[1].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
gatewayAPIImplementationConfig := fakeComponentHandlers[2].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
Expect(gatewayAPIImplementationConfig.CustomEnvoyGateway).NotTo(BeNil())
Expect(*gatewayAPIImplementationConfig.CustomEnvoyGateway).To(Equal(*envoyGateway))
})
Expand Down Expand Up @@ -467,12 +468,13 @@ var _ = Describe("Gateway API controller tests", func() {
Expect(err).ShouldNot(HaveOccurred())

By("checking the component handlers")
Expect(fakeComponentHandlers).To(HaveLen(2))
Expect(fakeComponentHandlers).To(HaveLen(3))
Expect(fakeComponentHandlers[0].createOnly).To(BeTrue())
Expect(fakeComponentHandlers[1].createOnly).To(BeFalse())
Expect(fakeComponentHandlers[2].createOnly).To(BeFalse())

By("checking that the custom EnvoyProxies were passed through")
gatewayAPIImplementationConfig := fakeComponentHandlers[1].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
gatewayAPIImplementationConfig := fakeComponentHandlers[2].lastComponent.(gatewayapi.GatewayAPIImplementationConfigInterface).GetConfig()
Expect(gatewayAPIImplementationConfig.CustomEnvoyProxies).NotTo(BeNil())
Expect(gatewayAPIImplementationConfig.CustomEnvoyProxies).To(HaveKeyWithValue("custom-class-1", envoyProxy1))
Expect(gatewayAPIImplementationConfig.CustomEnvoyProxies).To(HaveKeyWithValue("custom-class-2", envoyProxy2))
Expand Down Expand Up @@ -534,6 +536,56 @@ var _ = Describe("Gateway API controller tests", func() {
).Return()
_, err := r.Reconcile(ctx, reconcile.Request{})
Expect(err).Should(HaveOccurred())

By("checking the tigera-gateway namespace was still rendered before the EnvoyProxyRef failure")
// Regression: previously, an EnvoyProxyRef pointing at a not-yet-existent EnvoyProxy in
// tigera-gateway caused reconcile to early-return before the namespace was created,
// deadlocking the user (they couldn't create the EnvoyProxy because the namespace
// didn't exist). The namespace must now be rendered before EnvoyProxyRef resolution.
Expect(fakeComponentHandlers).To(HaveLen(2))
Expect(fakeComponentHandlers[0].createOnly).To(BeTrue())
Expect(fakeComponentHandlers[1].createOnly).To(BeFalse())
nsObjs, _ := fakeComponentHandlers[1].lastComponent.Objects()
Expect(nsObjs).To(HaveLen(1))
Expect(nsObjs[0]).To(BeAssignableToTypeOf(&corev1.Namespace{}))
Expect(nsObjs[0].GetName()).To(Equal("tigera-gateway"))
})

It("renders the tigera-gateway namespace when only an EnvoyProxyRef is configured and the EnvoyProxy is missing", func() {
Expect(c.Create(ctx, installation)).NotTo(HaveOccurred())

By("applying a GatewayAPI CR with an EnvoyProxyRef but no matching EnvoyProxy")
gwapi := &operatorv1.GatewayAPI{
ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"},
Spec: operatorv1.GatewayAPISpec{
GatewayClasses: []operatorv1.GatewayClassSpec{{
Name: "custom-class-1",
EnvoyProxyRef: &operatorv1.NamespacedName{
Namespace: "tigera-gateway",
Name: "missing-proxy",
},
}},
},
}
Expect(c.Create(ctx, gwapi)).NotTo(HaveOccurred())

By("triggering a reconcile")
mockStatus.On(
"SetDegraded",
operatorv1.ResourceReadError,
"Error reading EnvoyProxyRef",
"envoyproxies.gateway.envoyproxy.io \"missing-proxy\" not found",
mock.Anything,
).Return()
_, err := r.Reconcile(ctx, reconcile.Request{})
Expect(err).Should(HaveOccurred())

By("verifying the namespace component still ran (CRDs + namespace, no nonCRD impl)")
Expect(fakeComponentHandlers).To(HaveLen(2))
nsObjs, _ := fakeComponentHandlers[1].lastComponent.Objects()
Expect(nsObjs).To(HaveLen(1))
Expect(nsObjs[0]).To(BeAssignableToTypeOf(&corev1.Namespace{}))
Expect(nsObjs[0].GetName()).To(Equal("tigera-gateway"))
})

It("handles when both GatewayKind and an incompatible EnvoyProxy are specified", func() {
Expand Down
28 changes: 28 additions & 0 deletions pkg/render/gatewayapi/gateway_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,34 @@ func GatewayAPIImplementationComponent(cfg *GatewayAPIImplementationConfig) rend
}
}

// GatewayAPINamespaceComponent emits only the tigera-gateway Namespace. The controller renders
// this early — alongside CRDs and before EnvoyProxyRef resolution — so that users can create
// EnvoyProxy resources in tigera-gateway on a fresh install without the controller deadlocking
// on a missing namespace.
func GatewayAPINamespaceComponent(installation *operatorv1.InstallationSpec) render.Component {
return &gatewayAPINamespaceComponent{installation: installation}
}

type gatewayAPINamespaceComponent struct {
installation *operatorv1.InstallationSpec
}

func (c *gatewayAPINamespaceComponent) ResolveImages(*operatorv1.ImageSet) error { return nil }
func (c *gatewayAPINamespaceComponent) SupportedOSType() rmeta.OSType { return rmeta.OSTypeLinux }
func (c *gatewayAPINamespaceComponent) Ready() bool { return true }

func (c *gatewayAPINamespaceComponent) Objects() ([]client.Object, []client.Object) {
resources := GatewayAPIResources()
return []client.Object{
render.CreateNamespace(
resources.namespace.Name,
c.installation.KubernetesProvider,
render.PSSPrivileged, // Needed for HostPath volume to write logs to
c.installation.Azure,
),
}, nil
}

func (pr *gatewayAPIImplementationComponent) ResolveImages(is *operatorv1.ImageSet) error {
reg := pr.cfg.Installation.Registry
path := pr.cfg.Installation.ImagePath
Expand Down
Loading