|
| 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | + |
| 3 | +using Azure.Core; |
| 4 | +using Azure.Identity; |
| 5 | +using Azure.ResourceManager; |
| 6 | +using Azure.ResourceManager.Batch; |
| 7 | +using Azure.ResourceManager.Batch.Models; |
| 8 | +using System; |
| 9 | +using System.IO; |
| 10 | +using System.Linq; |
| 11 | +using System.Threading.Tasks; |
| 12 | + |
| 13 | +namespace Azure.Compute.Batch.Samples.HelloWorld |
| 14 | +{ |
| 15 | + public class HelloWorldSample |
| 16 | + { |
| 17 | + private ArmClient _armClient; |
| 18 | + private BatchClient _batchClient; |
| 19 | + |
| 20 | + /// <summary> |
| 21 | + /// Creates a pool with a configurable number of nodes, then submits tasks which print a 'Hello world' message. |
| 22 | + /// The resulting stdout.txt or stderr.txt (depending on each task's exit code) is then printed to the console. |
| 23 | + /// |
| 24 | + /// After running, the job will be terminated and the pool will be deleted. |
| 25 | + /// </summary> |
| 26 | + /// <param name="batchAccountResourceId">The ARM resource ID of the Batch account.</param> |
| 27 | + /// <returns>A task which completes when the sample has finished running.</returns> |
| 28 | + public async Task Run(string batchAccountResourceId) |
| 29 | + { |
| 30 | + var batchAccountIdentifier = ResourceIdentifier.Parse(batchAccountResourceId); |
| 31 | + |
| 32 | + var credential = new DefaultAzureCredential(); |
| 33 | + _armClient = new ArmClient(credential); |
| 34 | + |
| 35 | + BatchAccountResource batchAccount = await _armClient.GetBatchAccountResource(batchAccountIdentifier).GetAsync(); |
| 36 | + |
| 37 | + _batchClient = new BatchClient(new Uri($"https://{batchAccount.Data.AccountEndpoint}"), credential); |
| 38 | + |
| 39 | + var poolName = GenerateUniqueName("HelloWorldPool"); |
| 40 | + var imageReference = new BatchImageReference() |
| 41 | + { |
| 42 | + Publisher = "canonical", |
| 43 | + Offer = "0001-com-ubuntu-server-jammy", |
| 44 | + Sku = "22_04-lts", |
| 45 | + Version = "latest" |
| 46 | + }; |
| 47 | + string nodeAgentSku = "batch.node.ubuntu 22.04"; |
| 48 | + int targetDedicatedNodes = 1; |
| 49 | + |
| 50 | + Console.WriteLine("Creating pool {0} with {1} dedicated {2} in account {3}", poolName, targetDedicatedNodes, targetDedicatedNodes == 1 ? "node" : "nodes", batchAccount.Data.Name); |
| 51 | + BatchAccountPoolResource pool = (await batchAccount.GetBatchAccountPools().CreateOrUpdateAsync(WaitUntil.Completed, poolName, new BatchAccountPoolData() |
| 52 | + { |
| 53 | + VmSize = "Standard_DS1_v2", |
| 54 | + DeploymentConfiguration = new BatchDeploymentConfiguration() |
| 55 | + { |
| 56 | + VmConfiguration = new BatchVmConfiguration(imageReference, nodeAgentSku) |
| 57 | + }, |
| 58 | + ScaleSettings = new BatchAccountPoolScaleSettings() |
| 59 | + { |
| 60 | + FixedScale = new BatchAccountFixedScaleSettings() |
| 61 | + { |
| 62 | + TargetDedicatedNodes = targetDedicatedNodes |
| 63 | + } |
| 64 | + } |
| 65 | + })).Value; |
| 66 | + |
| 67 | + string jobId = GenerateUniqueName("HelloWorldJob"); |
| 68 | + |
| 69 | + try |
| 70 | + { |
| 71 | + Console.WriteLine("Creating job {0}", jobId); |
| 72 | + await _batchClient.CreateJobAsync(new BatchJobCreateOptions(jobId, new BatchPoolInfo() { PoolId = poolName })); |
| 73 | + |
| 74 | + for (int i = 0; i < 5; i++) |
| 75 | + { |
| 76 | + string taskId = $"task-{i}"; |
| 77 | + Console.WriteLine("Submitting {0}", taskId); |
| 78 | + await _batchClient.CreateTaskAsync(jobId, new BatchTaskCreateOptions(taskId, $"echo Hello world from {taskId}")); |
| 79 | + } |
| 80 | + |
| 81 | + Console.WriteLine("Waiting for all tasks to complete..."); |
| 82 | + await waitForTasksToComplete(jobId); |
| 83 | + |
| 84 | + var completedTasks = _batchClient.GetTasksAsync(jobId, filter: "state eq 'completed'"); |
| 85 | + await foreach (BatchTask t in completedTasks) |
| 86 | + { |
| 87 | + var outputFileName = t.ExecutionInfo.ExitCode == 0 ? "stdout.txt" : "stderr.txt"; |
| 88 | + |
| 89 | + Console.WriteLine("Task {0} exited with code {1}. Output ({2}):", |
| 90 | + t.Id, t.ExecutionInfo.ExitCode, outputFileName); |
| 91 | + |
| 92 | + BinaryData fileContents = await _batchClient.GetTaskFileAsync(jobId, t.Id, outputFileName); |
| 93 | + using (var reader = new StreamReader(fileContents.ToStream())) |
| 94 | + { |
| 95 | + Console.WriteLine(await reader.ReadLineAsync()); |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + finally |
| 100 | + { |
| 101 | + Console.WriteLine("Terminating job and deleting pool"); |
| 102 | + await Task.WhenAll([ |
| 103 | + _batchClient.TerminateJobAsync(jobId), |
| 104 | + pool.DeleteAsync(WaitUntil.Completed)]); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + /// <summary> |
| 109 | + /// Poll all the tasks in the given job and wait for them to reach the completed state. |
| 110 | + /// </summary> |
| 111 | + /// <param name="jobId">The ID of the job to poll</param> |
| 112 | + /// <returns>A task that will complete when all Batch tasks have completed.</returns> |
| 113 | + /// <exception cref="TimeoutException">Thrown if all tasks haven't reached the completed state after a certain period of time</exception> |
| 114 | + private async Task waitForTasksToComplete(String jobId) |
| 115 | + { |
| 116 | + // Note that this timeout should take into account the time it takes for the pool to scale up |
| 117 | + var timeoutAfter = DateTime.Now.AddMinutes(10); |
| 118 | + while (DateTime.Now < timeoutAfter) |
| 119 | + { |
| 120 | + var allComplete = true; |
| 121 | + var tasks = _batchClient.GetTasksAsync(jobId, select: ["id", "state"]); |
| 122 | + await foreach (BatchTask task in tasks) |
| 123 | + { |
| 124 | + if (task.State != BatchTaskState.Completed) |
| 125 | + { |
| 126 | + allComplete = false; |
| 127 | + break; |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + if (allComplete) |
| 132 | + { |
| 133 | + return; |
| 134 | + } |
| 135 | + |
| 136 | + await Task.Delay(10000); |
| 137 | + } |
| 138 | + |
| 139 | + throw new TimeoutException("Task(s) did not complete within the specified time"); |
| 140 | + } |
| 141 | + |
| 142 | + /// <summary> |
| 143 | + /// Generate a unique name with the given prefix using the current user name and a timestamp. |
| 144 | + /// </summary> |
| 145 | + /// <param name="prefix">The name's prefix.</param> |
| 146 | + /// <returns>The generated name.</returns> |
| 147 | + private static string GenerateUniqueName(string prefix) |
| 148 | + { |
| 149 | + string currentUser = new string(Environment.UserName.Where(char.IsLetterOrDigit).ToArray()); |
| 150 | + return string.Format("{0}-{1}-{2}", prefix, currentUser, DateTime.Now.ToString("yyyyMMdd-HHmmss")); |
| 151 | + } |
| 152 | + } |
| 153 | +} |
0 commit comments