-
Notifications
You must be signed in to change notification settings - Fork 497
CNTRLPLANE-3277: Add Azure OAuth LoadBalancer private topology e2e test #8584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
| }) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The CI run showed this test can't pass as-is — Interestingly the OAuth token flow itself succeeds because it routes through the guest kubeconfig transport — it's specifically the raw healthcheck that fails. So the underlying validation logic works; it's just the connectivity check that's incompatible with private topology. The fix needs to happen in the shared helper. A few approaches:
This needs to be addressed in the helper before this can land. |
||
| }) | ||
| } | ||
|
|
||
| // 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() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The public variant ( Registering this last is a reasonable mitigation, but declaration order between The cleanest fix that matches how the public variant is structured would be a dedicated |
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore cluster auth state after OAuth flow mutation.
Line 381 calls
ValidateOAuthWithIdentityProviderViaLoadBalancer, which mutates HostedCluster OAuth config and creates anhtpasswdSecret without restoring/deleting them. This makes outcomes order-dependent on shared clusters. Please capture original state and defer restoration, withapierrors.IsNotFound()handling during cleanup.As per coding guidelines: "When a test mutates cluster state, capture original state before mutation and defer restoration; check
apierrors.IsNotFound()in cleanup to handle already-deleted resources".🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mutation happens inside the shared helper
ValidateOAuthWithIdentityProviderViaLoadBalancer(intest/e2e/util/oauth.go), which is also used by the existingAzureOAuthLoadBalancerTest(line 284, same file) without cleanup. Adding cleanup in the caller would mean duplicating restore logic that belongs in the helper itself.This is a pre-existing concern, not introduced by this PR. Noted in the PR description as a known tradeoff. If we address it, it should be done in the shared helper as a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nirshal, that's a fair point — the concern is pre-existing and your reasoning is correct. Duplicating restore logic in each caller would be the wrong approach; the cleanup belongs insideValidateOAuthWithIdentityProviderViaLoadBalanceritself so all callers benefit automatically.Would you like me to open a follow-up GitHub issue to track adding htpasswd Secret cleanup (and OAuth config restoration) to the shared helper?
🧠 Learnings used