From 2cefac71be464a819f4e5eb0378f71cf73f1beee Mon Sep 17 00:00:00 2001 From: David Watrous Date: Tue, 30 Apr 2024 14:48:07 -0400 Subject: [PATCH] Update C# HelloWorld sample to track 2 --- .../01_HelloWorld/HelloWorld.csproj | 21 +-- .../01_HelloWorld/HelloWorld.sln | 13 +- .../01_HelloWorld/HelloWorldSample.cs | 150 +++++++++++++++ .../GettingStarted/01_HelloWorld/Program.cs | 171 +----------------- .../GettingStarted/01_HelloWorld/Settings.cs | 38 ---- .../01_HelloWorld/settings.json | 12 -- 6 files changed, 169 insertions(+), 236 deletions(-) create mode 100644 CSharp/GettingStarted/01_HelloWorld/HelloWorldSample.cs delete mode 100644 CSharp/GettingStarted/01_HelloWorld/Settings.cs delete mode 100644 CSharp/GettingStarted/01_HelloWorld/settings.json diff --git a/CSharp/GettingStarted/01_HelloWorld/HelloWorld.csproj b/CSharp/GettingStarted/01_HelloWorld/HelloWorld.csproj index 882a9762..c80e8d7c 100644 --- a/CSharp/GettingStarted/01_HelloWorld/HelloWorld.csproj +++ b/CSharp/GettingStarted/01_HelloWorld/HelloWorld.csproj @@ -2,25 +2,14 @@ Exe - net462 - Microsoft.Azure.Batch.Samples.HelloWorld + net8.0 + Azure.Compute.Batch.Samples.HelloWorld - - - - - - - - - - - - - PreserveNewest - + + + \ No newline at end of file diff --git a/CSharp/GettingStarted/01_HelloWorld/HelloWorld.sln b/CSharp/GettingStarted/01_HelloWorld/HelloWorld.sln index 67cb5a4e..6308683f 100644 --- a/CSharp/GettingStarted/01_HelloWorld/HelloWorld.sln +++ b/CSharp/GettingStarted/01_HelloWorld/HelloWorld.sln @@ -1,12 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.12 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloWorld", "HelloWorld.csproj", "{FE62D509-B9A9-4592-81BB-779306C22F95}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.Batch.Samples.Common", "..\..\Common\Microsoft.Azure.Batch.Samples.Common.csproj", "{612B170A-1697-4C40-BD57-26A6C8AC6534}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -17,12 +15,11 @@ Global {FE62D509-B9A9-4592-81BB-779306C22F95}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE62D509-B9A9-4592-81BB-779306C22F95}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE62D509-B9A9-4592-81BB-779306C22F95}.Release|Any CPU.Build.0 = Release|Any CPU - {612B170A-1697-4C40-BD57-26A6C8AC6534}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {612B170A-1697-4C40-BD57-26A6C8AC6534}.Debug|Any CPU.Build.0 = Debug|Any CPU - {612B170A-1697-4C40-BD57-26A6C8AC6534}.Release|Any CPU.ActiveCfg = Release|Any CPU - {612B170A-1697-4C40-BD57-26A6C8AC6534}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {33B77A65-5D1C-46B7-918E-D922919D6884} + EndGlobalSection EndGlobal diff --git a/CSharp/GettingStarted/01_HelloWorld/HelloWorldSample.cs b/CSharp/GettingStarted/01_HelloWorld/HelloWorldSample.cs new file mode 100644 index 00000000..43356b13 --- /dev/null +++ b/CSharp/GettingStarted/01_HelloWorld/HelloWorldSample.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Azure.Core; +using Azure.Identity; +using Azure.ResourceManager; +using Azure.ResourceManager.Batch; +using Azure.ResourceManager.Batch.Models; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Azure.Compute.Batch.Samples.HelloWorld +{ + public class HelloWorldSample + { + private ArmClient _armClient; + private BatchClient _batchClient; + + /// + /// Creates a pool with a configurable number of nodes, then submits tasks which print a 'Hello world' message. + /// The resulting stdout.txt or stderr.txt (depending on each task's exit code) is then printed to the console. + /// + /// After running, the job will be terminated and the pool will be deleted. + /// + /// The ARM resource ID of the Batch account. + /// A task which completes when the sample has finished running. + public async Task Run(string batchAccountResourceId) + { + var batchAccountIdentifier = ResourceIdentifier.Parse(batchAccountResourceId); + + var credential = new DefaultAzureCredential(); + _armClient = new ArmClient(credential); + + BatchAccountResource batchAccount = await _armClient.GetBatchAccountResource(batchAccountIdentifier).GetAsync(); + + _batchClient = new BatchClient(new Uri($"https://{batchAccount.Data.AccountEndpoint}"), credential); + + var poolName = GenerateUniqueName("HelloWorldPool"); + var imageReference = new BatchImageReference() + { + Publisher = "canonical", + Offer = "0001-com-ubuntu-server-jammy", + Sku = "22_04-lts", + Version = "latest" + }; + string nodeAgentSku = "batch.node.ubuntu 22.04"; + + BatchAccountPoolResource pool = (await batchAccount.GetBatchAccountPools().CreateOrUpdateAsync(WaitUntil.Completed, poolName, new BatchAccountPoolData() + { + VmSize = "Standard_DS1_v2", + DeploymentConfiguration = new BatchDeploymentConfiguration() + { + VmConfiguration = new BatchVmConfiguration(imageReference, nodeAgentSku) + }, + ScaleSettings = new BatchAccountPoolScaleSettings() + { + FixedScale = new BatchAccountFixedScaleSettings() + { + TargetDedicatedNodes = 1 + } + } + })).Value; + + string jobId = GenerateUniqueName("HelloWorldJob"); + + try + { + await _batchClient.CreateJobAsync(new BatchJobCreateContent(jobId, new BatchPoolInfo() { PoolId = poolName })); + + for (int i = 0; i < 5; i++) + { + string taskId = $"task-{i}"; + Console.WriteLine("Submitting {0}", taskId); + await _batchClient.CreateTaskAsync(jobId, new BatchTaskCreateContent(taskId, $"echo Hello world from {taskId}")); + } + + Console.WriteLine("Waiting for all tasks to complete on job: {0} ...", jobId); + await waitForTasksToComplete(jobId); + + var completedTasks = _batchClient.GetTasksAsync(jobId, filter: "state eq 'completed'"); + await foreach (BatchTask t in completedTasks) + { + var outputFileName = t.ExecutionInfo.ExitCode == 0 ? "stdout.txt" : "stderr.txt"; + + Console.WriteLine("Task {0} exited with code {1}. Output ({2}):", + t.Id, t.ExecutionInfo.ExitCode, outputFileName); + + BinaryData fileContents = await _batchClient.GetTaskFileAsync(jobId, t.Id, outputFileName); + using (var reader = new StreamReader(fileContents.ToStream())) + { + Console.WriteLine(await reader.ReadLineAsync()); + } + } + } + finally + { + Console.WriteLine("Terminating job {0} and deleting pool {1}", jobId, poolName); + await Task.WhenAll([ + _batchClient.TerminateJobAsync(jobId), + pool.DeleteAsync(WaitUntil.Completed)]); + } + } + + /// + /// Poll all the tasks in the given job and wait for them to reach the completed state. + /// + /// The ID of the job to poll + /// A task that will complete when all Batch tasks have completed. + /// Thrown if all tasks haven't reached the completed state after a certain period of time + private async Task waitForTasksToComplete(String jobId) + { + // Note that this timeout should take into account the time it takes for the pool to scale up + var timeoutAfter = DateTime.Now.AddMinutes(10); + while (DateTime.Now < timeoutAfter) + { + var allComplete = true; + var tasks = _batchClient.GetTasksAsync(jobId, select: ["id", "state"]); + await foreach (BatchTask task in tasks) + { + if (task.State != BatchTaskState.Completed) + { + allComplete = false; + break; + } + } + + if (allComplete) + { + return; + } + + await Task.Delay(10000); + } + + throw new TimeoutException("Task(s) did not complete within the specified time"); + } + + /// + /// Generate a unique name with the given prefix using the current user name and a timestamp. + /// + /// The name's prefix. + /// The generated name. + private static string GenerateUniqueName(string prefix) + { + string currentUser = new string(Environment.UserName.Where(char.IsLetterOrDigit).ToArray()); + return string.Format("{0}-{1}-{2}", prefix, currentUser, DateTime.Now.ToString("yyyyMMdd-HHmmss")); + } + } +} diff --git a/CSharp/GettingStarted/01_HelloWorld/Program.cs b/CSharp/GettingStarted/01_HelloWorld/Program.cs index 3a079426..bf0338e6 100644 --- a/CSharp/GettingStarted/01_HelloWorld/Program.cs +++ b/CSharp/GettingStarted/01_HelloWorld/Program.cs @@ -1,176 +1,23 @@ -//Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation. All rights reserved. -namespace Microsoft.Azure.Batch.Samples.HelloWorld -{ - using System; - using System.IO; - using System.Collections.Generic; - using System.Threading.Tasks; - using Auth; - using Batch.Common; - using Common; - using Microsoft.Extensions.Configuration; +using System; +using System.Threading.Tasks; +namespace Azure.Compute.Batch.Samples.HelloWorld +{ /// /// The main program of the HelloWorld sample /// public static class Program { - public static void Main(string[] args) - { - try - { - AccountSettings accountSettings = SampleHelpers.LoadAccountSettings(); - Settings helloWorldSettings = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("settings.json") - .Build() - .Get(); - - HelloWorldAsync(accountSettings, helloWorldSettings).Wait(); - } - catch (AggregateException aggregateException) - { - // Go through all exceptions and dump useful information - foreach (Exception exception in aggregateException.InnerExceptions) - { - Console.WriteLine(exception.ToString()); - Console.WriteLine(); - } + private static string batchAccountResourceId = "your-batch-account-resource-id"; - throw; - } + public async static Task Main(string[] args) + { + await new HelloWorldSample().Run(batchAccountResourceId); Console.WriteLine("Press return to exit..."); Console.ReadLine(); } - - /// - /// Submits a job to the Azure Batch service, and waits for it to complete - /// - private static async Task HelloWorldAsync( - AccountSettings accountSettings, - Settings helloWorldConfigurationSettings) - { - Console.WriteLine("Running with the following settings: "); - Console.WriteLine("-------------------------------------"); - Console.WriteLine(helloWorldConfigurationSettings.ToString()); - Console.WriteLine(accountSettings.ToString()); - - // Set up the Batch Service credentials used to authenticate with the Batch Service. - BatchSharedKeyCredentials credentials = new BatchSharedKeyCredentials( - accountSettings.BatchServiceUrl, - accountSettings.BatchAccountName, - accountSettings.BatchAccountKey); - - // Get an instance of the BatchClient for a given Azure Batch account. - using (BatchClient batchClient = BatchClient.Open(credentials)) - { - // add a retry policy. The built-in policies are No Retry (default), Linear Retry, and Exponential Retry - batchClient.CustomBehaviors.Add(RetryPolicyProvider.ExponentialRetryProvider(TimeSpan.FromSeconds(5), 3)); - - string jobId = GettingStartedCommon.CreateJobId("HelloWorldJob"); - - try - { - // Submit the job - await SubmitJobAsync(batchClient, helloWorldConfigurationSettings, jobId); - - // Wait for the job to complete - await WaitForJobAndPrintOutputAsync(batchClient, jobId); - } - finally - { - // Delete the job to ensure the tasks are cleaned up - if (!string.IsNullOrEmpty(jobId) && helloWorldConfigurationSettings.ShouldDeleteJob) - { - Console.WriteLine("Deleting job: {0}", jobId); - await batchClient.JobOperations.DeleteJobAsync(jobId); - } - } - } - } - - /// - /// Creates a job and adds a task to it. - /// - /// The BatchClient to use when interacting with the Batch service. - /// The configuration settings - /// The ID of the job. - /// An asynchronous representing the operation. - private static async Task SubmitJobAsync( - BatchClient batchClient, - Settings configurationSettings, - string jobId) - { - // create an empty unbound Job - CloudJob unboundJob = batchClient.JobOperations.CreateJob(); - unboundJob.Id = jobId; - - // For this job, ask the Batch service to automatically create a pool of VMs when the job is submitted. - unboundJob.PoolInformation = new PoolInformation() - { - AutoPoolSpecification = new AutoPoolSpecification() - { - AutoPoolIdPrefix = "HelloWorld", - PoolSpecification = new PoolSpecification() - { - TargetDedicatedComputeNodes = configurationSettings.PoolTargetNodeCount, - VirtualMachineSize = configurationSettings.PoolNodeVirtualMachineSize, - VirtualMachineConfiguration = new VirtualMachineConfiguration( - imageReference: new ImageReference( - publisher: configurationSettings.ImagePublisher, - offer: configurationSettings.ImageOffer, - sku: configurationSettings.ImageSku, - version: configurationSettings.ImageVersion - ), - nodeAgentSkuId: configurationSettings.NodeAgentSkuId), - }, - KeepAlive = false, - PoolLifetimeOption = PoolLifetimeOption.Job - } - }; - - // Commit Job to create it in the service - await unboundJob.CommitAsync(); - - // create a simple task. Each task within a job must have a unique ID - await batchClient.JobOperations.AddTaskAsync(jobId, new CloudTask("task1", "cmd /c echo Hello world from the Batch Hello world sample!")); - } - - /// - /// Waits for all tasks under the specified job to complete and then prints each task's output to the console. - /// - /// The BatchClient to use when interacting with the Batch service. - /// The ID of the job. - /// An asynchronous representing the operation. - private static async Task WaitForJobAndPrintOutputAsync(BatchClient batchClient, string jobId) - { - Console.WriteLine("Waiting for all tasks to complete on job: {0} ...", jobId); - - // We use the task state monitor to monitor the state of our tasks -- in this case we will wait for them all to complete. - TaskStateMonitor taskStateMonitor = batchClient.Utilities.CreateTaskStateMonitor(); - - List ourTasks = await batchClient.JobOperations.ListTasks(jobId).ToListAsync(); - - // Wait for all tasks to reach the completed state. - // If the pool is being resized then enough time is needed for the nodes to reach the idle state in order - // for tasks to run on them. - await taskStateMonitor.WhenAll(ourTasks, TaskState.Completed, TimeSpan.FromMinutes(10)); - - // dump task output - foreach (CloudTask t in ourTasks) - { - Console.WriteLine("Task {0}", t.Id); - - //Read the standard out of the task - NodeFile standardOutFile = await t.GetNodeFileAsync(Constants.StandardOutFileName); - string standardOutText = await standardOutFile.ReadAsStringAsync(); - Console.WriteLine("Standard out:"); - Console.WriteLine(standardOutText); - - Console.WriteLine(); - } - } } } diff --git a/CSharp/GettingStarted/01_HelloWorld/Settings.cs b/CSharp/GettingStarted/01_HelloWorld/Settings.cs deleted file mode 100644 index cb7d6d70..00000000 --- a/CSharp/GettingStarted/01_HelloWorld/Settings.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Microsoft.Azure.Batch.Samples.HelloWorld -{ - using System.Text; - using Common; - - public partial class Settings - { - public string PoolId { get; set; } - public int PoolTargetNodeCount { get; set; } - public string PoolOSFamily { get; set; } - public string PoolNodeVirtualMachineSize { get; set; } - public bool ShouldDeleteJob { get; set; } - public string ImagePublisher { get; set; } - public string ImageOffer { get; set; } - public string ImageSku { get; set; } - public string ImageVersion { get; set; } - public string NodeAgentSkuId { get; set; } - - public override string ToString() - { - StringBuilder stringBuilder = new StringBuilder(); - - SampleHelpers.AddSetting(stringBuilder, "PoolId", this.PoolId); - SampleHelpers.AddSetting(stringBuilder, "PoolTargetNodeCount", this.PoolTargetNodeCount); - SampleHelpers.AddSetting(stringBuilder, "PoolOSFamily", this.PoolOSFamily); - SampleHelpers.AddSetting(stringBuilder, "PoolNodeVirtualMachineSize", this.PoolNodeVirtualMachineSize); - SampleHelpers.AddSetting(stringBuilder, "ShouldDeleteJob", this.ShouldDeleteJob); - SampleHelpers.AddSetting(stringBuilder, "ImagePublisher", this.ImagePublisher); - SampleHelpers.AddSetting(stringBuilder, "ImageOffer", this.ImageOffer); - SampleHelpers.AddSetting(stringBuilder, "ImageSku", this.ImageSku); - SampleHelpers.AddSetting(stringBuilder, "ImageVersion", this.ImageVersion); - SampleHelpers.AddSetting(stringBuilder, "NodeAgentSkuId", this.NodeAgentSkuId); - - - return stringBuilder.ToString(); - } - } -} diff --git a/CSharp/GettingStarted/01_HelloWorld/settings.json b/CSharp/GettingStarted/01_HelloWorld/settings.json deleted file mode 100644 index 39747d66..00000000 --- a/CSharp/GettingStarted/01_HelloWorld/settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "poolTargetNodeCount": 2, - "poolNodeVirtualMachineSize": "standard_d2_v3", - "poolOSFamily": "5", - "poolId": "HelloWorld-Pool", - "shouldDeleteJob": true, - "imagePublisher": "MicrosoftWindowsServer", - "imageOffer": "WindowsServer", - "imageSku": "2016-Datacenter-smalldisk", - "imageVersion": "latest", - "nodeAgentSkuId": "batch.node.windows amd64" -}