From 5d52eb071824f3a9c84ae695114141b4e41cb951 Mon Sep 17 00:00:00 2001 From: Alessandro Rossi Date: Mon, 25 May 2026 17:51:48 +0200 Subject: [PATCH] test(e2ev2): add Azure OAuth LoadBalancer private topology test Add e2e validation for OAuth LoadBalancer publishing on Azure Private topology. Reuse the existing private cluster variant by adding --oauth-publishing-strategy=LoadBalancer to its creation args, avoiding an additional Azure cluster in CI. Signed-off-by: Alessandro Rossi Commit-Message-Assisted-by: Claude (via Claude Code) --- test/e2e/v2/lifecycle/azure.go | 3 +- .../e2e/v2/tests/hosted_cluster_azure_test.go | 98 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/test/e2e/v2/lifecycle/azure.go b/test/e2e/v2/lifecycle/azure.go index 47c1d6e1495..732ca8bc1f3 100644 --- a/test/e2e/v2/lifecycle/azure.go +++ b/test/e2e/v2/lifecycle/azure.go @@ -103,6 +103,7 @@ func (a *AzurePlatformConfig) ClusterSpecs(releaseImage, n1Image string) []Clust ExtraArgs: []string{ "--endpoint-access=Private", "--endpoint-access-private-nat-subnet-id=" + a.privateNATSubnetID, + "--oauth-publishing-strategy=LoadBalancer", }, }, { @@ -194,7 +195,7 @@ func (a *AzurePlatformConfig) TestMatrix(releaseImage string) TestMatrix { { Name: "private", ClusterFile: "cluster-name-private", - LabelFilter: "self-managed-azure-private", + LabelFilter: "self-managed-azure-private || self-managed-azure-oauth-lb-private", JUnitFile: "junit_self_managed_azure_private.xml", }, { diff --git a/test/e2e/v2/tests/hosted_cluster_azure_test.go b/test/e2e/v2/tests/hosted_cluster_azure_test.go index b3a3c49944e..943c4d849fb 100644 --- a/test/e2e/v2/tests/hosted_cluster_azure_test.go +++ b/test/e2e/v2/tests/hosted_cluster_azure_test.go @@ -286,11 +286,109 @@ func AzureOAuthLoadBalancerTest(getTestCtx internal.TestContextGetter) { }) } +// AzureOAuthLoadBalancerPrivateTest registers tests for Azure OAuth LoadBalancer +// publishing validation in a private topology cluster. +// These tests verify that: +// - The oauth-openshift Service is created as a LoadBalancer with an allocated endpoint +// - The Service carries the Azure internal LoadBalancer annotation +// - The OAuth token flow (kubeadmin + htpasswd IDP) works through that endpoint +// +// The OAuth token flow test requires the test runner to have network connectivity +// to the Azure VNet (e.g., running in the management cluster or via VPN/peering). +func AzureOAuthLoadBalancerPrivateTest(getTestCtx internal.TestContextGetter) { + Context("Azure OAuth LoadBalancer in Private Topology", Label("Azure", "self-managed-azure-oauth-lb-private"), Ordered, func() { + var testCtx *internal.TestContext + var controlPlaneNamespace string + var hc *hyperv1.HostedCluster + + BeforeAll(func() { + testCtx = getTestCtx() + hc = testCtx.GetHostedCluster() + if hc == nil || hc.Spec.Platform.Type != hyperv1.AzurePlatform { + Skip("Azure OAuth LB Private tests are only for Azure platform") + } + if hc.Spec.Platform.Azure == nil || hc.Spec.Platform.Azure.Topology != hyperv1.AzureTopologyPrivate { + Skip("Azure OAuth LB Private tests require Private topology") + } + controlPlaneNamespace = testCtx.ControlPlaneNamespace + Expect(controlPlaneNamespace).NotTo(BeEmpty(), "control plane namespace must be set") + + strategy := netutil.ServicePublishingStrategyByTypeByHC(hc, hyperv1.OAuthServer) + if strategy == nil || strategy.Type != hyperv1.LoadBalancer { + Skip("Azure OAuth LB Private tests require OAuthServer with LoadBalancer publishing strategy") + } + }) + + It("should create oauth-openshift Service as LoadBalancer with an allocated endpoint", func() { + ctx := testCtx.Context + + e2eutil.EventuallyObject(GinkgoTB(), ctx, "oauth-openshift Service is LoadBalancer with an allocated endpoint", + func(ctx context.Context) (*corev1.Service, error) { + svc := hcpmanifests.OauthServerService(controlPlaneNamespace) + err := testCtx.MgmtClient.Get(ctx, crclient.ObjectKeyFromObject(svc), svc) + return svc, err + }, + []e2eutil.Predicate[*corev1.Service]{ + func(svc *corev1.Service) (done bool, reasons string, err error) { + if svc.Spec.Type != corev1.ServiceTypeLoadBalancer { + return false, fmt.Sprintf("expected Service type LoadBalancer, got %s", svc.Spec.Type), nil + } + return true, "oauth-openshift Service is type LoadBalancer", nil + }, + func(svc *corev1.Service) (done bool, reasons string, err error) { + if len(svc.Status.LoadBalancer.Ingress) == 0 { + return false, "LoadBalancer has no ingress entries yet", nil + } + ingress := svc.Status.LoadBalancer.Ingress[0] + if ingress.IP == "" && ingress.Hostname == "" { + return false, "LoadBalancer ingress has no IP or hostname", nil + } + host := ingress.IP + if host == "" { + host = ingress.Hostname + } + return true, fmt.Sprintf("oauth-openshift LoadBalancer has an allocated endpoint: %s", host), nil + }, + }, + e2eutil.WithTimeout(10*time.Minute), + ) + }) + + It("should have Azure internal LB annotation on oauth-openshift Service", func() { + ctx := testCtx.Context + e2eutil.EventuallyObject(GinkgoTB(), ctx, "oauth-openshift Service has Azure internal LB annotation", + func(ctx context.Context) (*corev1.Service, error) { + svc := hcpmanifests.OauthServerService(controlPlaneNamespace) + err := testCtx.MgmtClient.Get(ctx, crclient.ObjectKeyFromObject(svc), svc) + return svc, err + }, + []e2eutil.Predicate[*corev1.Service]{ + func(svc *corev1.Service) (done bool, reasons string, err error) { + val, ok := svc.Annotations[azureutil.InternalLoadBalancerAnnotation] + if !ok || val != azureutil.InternalLoadBalancerValue { + return false, fmt.Sprintf("expected annotation %q to be %q, got %q (present: %v)", + azureutil.InternalLoadBalancerAnnotation, azureutil.InternalLoadBalancerValue, val, ok), nil + } + return true, "oauth-openshift Service has internal LB annotation", nil + }, + }, + e2eutil.WithTimeout(10*time.Minute), + ) + }) + + It("should complete OAuth token flow through LoadBalancer endpoint", func() { + ctx := testCtx.Context + e2eutil.ValidateOAuthWithIdentityProviderViaLoadBalancer(GinkgoTB(), ctx, testCtx.MgmtClient, hc) + }) + }) +} + // RegisterHostedClusterAzureTests registers all Azure-specific hosted cluster tests. func RegisterHostedClusterAzureTests(getTestCtx internal.TestContextGetter) { AzurePublicClusterTest(getTestCtx) AzurePrivateTopologyTest(getTestCtx) AzureOAuthLoadBalancerTest(getTestCtx) + AzureOAuthLoadBalancerPrivateTest(getTestCtx) } var _ = Describe("Hosted Cluster Azure", Label("hosted-cluster-azure"), func() {