-
Notifications
You must be signed in to change notification settings - Fork 934
Add AKS starter deployment E2E test #14351
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
Changes from 1 commit
43cbdd1
6ef79de
7b754c1
886b180
4318b51
d38acb6
f46b53e
f175bca
3ab65aa
da42bb7
1057b42
45adcbb
c2aa4d7
41dd194
d1d6551
1934af2
a9bbfb9
f3aed68
5fa81a7
7d3be48
dd62bbf
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 |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Aspire.Cli.Tests.Utils; | ||
| using Aspire.Deployment.EndToEnd.Tests.Helpers; | ||
| using Hex1b; | ||
| using Hex1b.Automation; | ||
| using Xunit; | ||
|
|
||
| namespace Aspire.Deployment.EndToEnd.Tests; | ||
|
|
||
| /// <summary> | ||
| /// End-to-end tests for deploying Aspire applications to Azure Kubernetes Service (AKS). | ||
| /// </summary> | ||
| public sealed class AksStarterDeploymentTests(ITestOutputHelper output) | ||
| { | ||
| // Timeout set to 45 minutes to allow for AKS provisioning (~10-15 min) plus deployment. | ||
| private static readonly TimeSpan s_testTimeout = TimeSpan.FromMinutes(45); | ||
|
|
||
| [Fact] | ||
| public async Task DeployStarterTemplateToAks() | ||
| { | ||
| using var cts = new CancellationTokenSource(s_testTimeout); | ||
| using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( | ||
| cts.Token, TestContext.Current.CancellationToken); | ||
| var cancellationToken = linkedCts.Token; | ||
|
|
||
| await DeployStarterTemplateToAksCore(cancellationToken); | ||
| } | ||
|
|
||
| private async Task DeployStarterTemplateToAksCore(CancellationToken cancellationToken) | ||
| { | ||
| // Validate prerequisites | ||
| var subscriptionId = AzureAuthenticationHelpers.TryGetSubscriptionId(); | ||
| if (string.IsNullOrEmpty(subscriptionId)) | ||
| { | ||
| Assert.Skip("Azure subscription not configured. Set ASPIRE_DEPLOYMENT_TEST_SUBSCRIPTION."); | ||
| } | ||
|
|
||
| if (!AzureAuthenticationHelpers.IsAzureAuthAvailable()) | ||
| { | ||
| if (DeploymentE2ETestHelpers.IsRunningInCI) | ||
| { | ||
| Assert.Fail("Azure authentication not available in CI. Check OIDC configuration."); | ||
| } | ||
| else | ||
| { | ||
| Assert.Skip("Azure authentication not available. Run 'az login' to authenticate."); | ||
| } | ||
| } | ||
|
|
||
| var workspace = TemporaryWorkspace.Create(output); | ||
| var recordingPath = DeploymentE2ETestHelpers.GetTestResultsRecordingPath(nameof(DeployStarterTemplateToAks)); | ||
| var startTime = DateTime.UtcNow; | ||
|
|
||
| // Generate unique names for Azure resources | ||
| var resourceGroupName = DeploymentE2ETestHelpers.GenerateResourceGroupName("aks"); | ||
| var clusterName = $"aks-{DeploymentE2ETestHelpers.GetRunId()}-{DeploymentE2ETestHelpers.GetRunAttempt()}"; | ||
| // ACR names must be alphanumeric only, 5-50 chars, globally unique | ||
| var acrName = $"acr{DeploymentE2ETestHelpers.GetRunId()}{DeploymentE2ETestHelpers.GetRunAttempt()}".ToLowerInvariant(); | ||
| // Ensure ACR name is valid (alphanumeric, 5-50 chars) | ||
| acrName = new string(acrName.Where(char.IsLetterOrDigit).Take(50).ToArray()); | ||
| if (acrName.Length < 5) | ||
| { | ||
| acrName = $"acrtest{Guid.NewGuid():N}"[..24]; | ||
| } | ||
|
|
||
| output.WriteLine($"Test: {nameof(DeployStarterTemplateToAks)}"); | ||
| output.WriteLine($"Resource Group: {resourceGroupName}"); | ||
| output.WriteLine($"AKS Cluster: {clusterName}"); | ||
| output.WriteLine($"ACR Name: {acrName}"); | ||
| output.WriteLine($"Subscription: {subscriptionId[..8]}..."); | ||
| output.WriteLine($"Workspace: {workspace.WorkspaceRoot.FullName}"); | ||
|
|
||
| try | ||
| { | ||
| var builder = Hex1bTerminal.CreateBuilder() | ||
| .WithHeadless() | ||
| .WithDimensions(160, 48) | ||
| .WithAsciinemaRecording(recordingPath) | ||
| .WithPtyProcess("/bin/bash", ["--norc"]); | ||
|
|
||
| using var terminal = builder.Build(); | ||
| var pendingRun = terminal.RunAsync(cancellationToken); | ||
|
|
||
| var counter = new SequenceCounter(); | ||
| var sequenceBuilder = new Hex1bTerminalInputSequenceBuilder(); | ||
|
|
||
| // Step 1: Prepare environment | ||
| output.WriteLine("Step 1: Preparing environment..."); | ||
| sequenceBuilder.PrepareEnvironment(workspace, counter); | ||
|
|
||
| // Step 2: Create resource group | ||
| output.WriteLine("Step 2: Creating resource group..."); | ||
| sequenceBuilder | ||
| .Type($"az group create --name {resourceGroupName} --location westus3 --output table") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60)); | ||
|
|
||
| // Step 3: Create Azure Container Registry | ||
| output.WriteLine("Step 3: Creating Azure Container Registry..."); | ||
| sequenceBuilder | ||
| .Type($"az acr create --resource-group {resourceGroupName} --name {acrName} --sku Basic --output table") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(3)); | ||
|
|
||
| // Step 4: Create AKS cluster with ACR attached | ||
| // Using minimal configuration: 1 node, Standard_B2s (smallest viable) | ||
| output.WriteLine("Step 4: Creating AKS cluster (this may take 10-15 minutes)..."); | ||
| sequenceBuilder | ||
| .Type($"az aks create " + | ||
| $"--resource-group {resourceGroupName} " + | ||
| $"--name {clusterName} " + | ||
| $"--node-count 1 " + | ||
| $"--node-vm-size Standard_B2s " + | ||
| $"--generate-ssh-keys " + | ||
| $"--attach-acr {acrName} " + | ||
| $"--enable-managed-identity " + | ||
| $"--output table") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(20)); | ||
|
|
||
| // Step 5: Configure kubectl credentials | ||
| output.WriteLine("Step 5: Configuring kubectl credentials..."); | ||
| sequenceBuilder | ||
| .Type($"az aks get-credentials --resource-group {resourceGroupName} --name {clusterName} --overwrite-existing") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(30)); | ||
|
|
||
| // Step 6: Verify kubectl connectivity | ||
| output.WriteLine("Step 6: Verifying kubectl connectivity..."); | ||
| sequenceBuilder | ||
| .Type("kubectl get nodes") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(30)); | ||
|
|
||
| // Step 7: Verify cluster is healthy | ||
| output.WriteLine("Step 7: Verifying cluster health..."); | ||
| sequenceBuilder | ||
| .Type("kubectl cluster-info") | ||
| .Enter() | ||
| .WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(30)); | ||
|
|
||
| // Step 8: Exit terminal | ||
| sequenceBuilder | ||
| .Type("exit") | ||
| .Enter(); | ||
|
|
||
| var sequence = sequenceBuilder.Build(); | ||
| await sequence.ApplyAsync(terminal, cancellationToken); | ||
| await pendingRun; | ||
|
|
||
| var duration = DateTime.UtcNow - startTime; | ||
| output.WriteLine($"AKS cluster creation and verification completed in {duration}"); | ||
|
|
||
| // Report success | ||
| DeploymentReporter.ReportDeploymentSuccess( | ||
| nameof(DeployStarterTemplateToAks), | ||
| resourceGroupName, | ||
| new Dictionary<string, string> | ||
| { | ||
| ["cluster"] = clusterName, | ||
| ["acr"] = acrName | ||
| }, | ||
| duration); | ||
|
|
||
| output.WriteLine("✅ Phase 1 Test passed - AKS cluster created and verified!"); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| var duration = DateTime.UtcNow - startTime; | ||
| output.WriteLine($"❌ Test failed after {duration}: {ex.Message}"); | ||
|
|
||
| DeploymentReporter.ReportDeploymentFailure( | ||
| nameof(DeployStarterTemplateToAks), | ||
| resourceGroupName, | ||
| ex.Message, | ||
| ex.StackTrace); | ||
|
|
||
| throw; | ||
| } | ||
| finally | ||
| { | ||
| // Clean up the resource group we created (includes AKS cluster and ACR) | ||
| output.WriteLine($"Triggering cleanup of resource group: {resourceGroupName}"); | ||
| TriggerCleanupResourceGroup(resourceGroupName, output); | ||
| DeploymentReporter.ReportCleanupStatus(resourceGroupName, success: true, "Cleanup triggered (fire-and-forget)"); | ||
|
||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Triggers cleanup of a specific resource group. | ||
| /// This is fire-and-forget - the hourly cleanup workflow handles any missed resources. | ||
| /// </summary> | ||
| private static void TriggerCleanupResourceGroup(string resourceGroupName, ITestOutputHelper output) | ||
| { | ||
| var process = new System.Diagnostics.Process | ||
| { | ||
| StartInfo = new System.Diagnostics.ProcessStartInfo | ||
| { | ||
| FileName = "az", | ||
| Arguments = $"group delete --name {resourceGroupName} --yes --no-wait", | ||
| RedirectStandardOutput = true, | ||
| RedirectStandardError = true, | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| } | ||
| }; | ||
|
|
||
| try | ||
| { | ||
| process.Start(); | ||
| output.WriteLine($"Cleanup triggered for resource group: {resourceGroupName}"); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| output.WriteLine($"Failed to trigger cleanup: {ex.Message}"); | ||
| } | ||
|
||
| } | ||
|
||
| } | ||
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 timeout comment mentions "plus deployment" but Phase 1 (current implementation) only performs infrastructure provisioning and verification, not deployment. The actual operations sum to approximately 25 minutes maximum (20 min AKS creation + ~5 min for other operations). While the 45-minute timeout may be intended for future phases, the comment could be misleading for the current implementation. Consider updating the comment to reflect the current phase or noting that the timeout accounts for future deployment steps.