diff --git a/README.md b/README.md index cc269ef1224..eab23b86ae4 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Welcome to the Microsoft Quantum Development Kit! This repository contains the runtime components for the [Quantum Development Kit](https://docs.microsoft.com/quantum/). It consists of the libraries and packages needed to create and simulate quantum applications using Q#. +- **[Azure/](./src/Azure/)**: Source for client package to create and manage jobs in Azure Quantum. - **[Simulation/](./src/Simulation/)**: Source for Q# simulation. Includes code generation, full-state and other simulators. - **[xUnit/](./src/Xunit/)**: Source for the xUnit's Q# test-case discoverer. diff --git a/Simulation.sln b/Simulation.sln index c5a58260186..2242d066ddd 100644 --- a/Simulation.sln +++ b/Simulation.sln @@ -3,6 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28809.33 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{99E234BC-997E-4E63-9F5C-3C3977543404}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure", "Azure", "{A1E878CB-ADF1-457A-9223-06F96ED8F7A6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.Quantum.Client", "src\Azure\Azure.Quantum.Client\Microsoft.Azure.Quantum.Client.csproj", "{DF654202-0008-4CDE-B35E-018CEFD0FC68}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Azure.Quantum.Client.Test", "src\Azure\Azure.Quantum.Client.Test\Microsoft.Azure.Quantum.Client.Test.csproj", "{1467128C-90E4-4723-B5C7-9469B83F54A6}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Runtime.Core", "src\Simulation\Core\Microsoft.Quantum.Runtime.Core.csproj", "{E9123D45-C1B0-4462-8810-D26ED6D31944}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime", "src\Simulation\QCTraceSimulator\Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj", "{058CB08D-BFA7-41E2-BE6B-0A0A72054F91}" @@ -39,6 +47,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProject EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QsharpExe", "src\Simulation\Simulators.Tests\TestProjects\QsharpExe\QsharpExe.csproj", "{2F5796A7-4AF8-4B78-928A-0A3A80752F9D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntryPointDriver", "src\Simulation\EntryPointDriver\EntryPointDriver.csproj", "{944FE7EF-9220-4CC6-BB20-CE517195B922}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests.EntryPointDriver", "src\Simulation\EntryPointDriver.Tests\Tests.EntryPointDriver.fsproj", "{E2F30496-19D8-46A8-9BC0-26936FFE70D2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +63,38 @@ Global RelWithDebInfo|x64 = RelWithDebInfo|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Debug|x64.Build.0 = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Release|Any CPU.Build.0 = Release|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Release|x64.ActiveCfg = Release|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.Release|x64.Build.0 = Release|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {DF654202-0008-4CDE-B35E-018CEFD0FC68}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Debug|x64.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Release|Any CPU.Build.0 = Release|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Release|x64.ActiveCfg = Release|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.Release|x64.Build.0 = Release|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {1467128C-90E4-4723-B5C7-9469B83F54A6}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU {E9123D45-C1B0-4462-8810-D26ED6D31944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9123D45-C1B0-4462-8810-D26ED6D31944}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9123D45-C1B0-4462-8810-D26ED6D31944}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -259,6 +303,38 @@ Global {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {2F5796A7-4AF8-4B78-928A-0A3A80752F9D}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Debug|Any CPU.Build.0 = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Debug|x64.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Debug|x64.Build.0 = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Release|Any CPU.ActiveCfg = Release|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Release|Any CPU.Build.0 = Release|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Release|x64.ActiveCfg = Release|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.Release|x64.Build.0 = Release|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {944FE7EF-9220-4CC6-BB20-CE517195B922}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Debug|x64.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Release|Any CPU.Build.0 = Release|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Release|x64.ActiveCfg = Release|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.Release|x64.Build.0 = Release|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.RelWithDebInfo|Any CPU.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.RelWithDebInfo|Any CPU.Build.0 = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.RelWithDebInfo|x64.ActiveCfg = Debug|Any CPU + {E2F30496-19D8-46A8-9BC0-26936FFE70D2}.RelWithDebInfo|x64.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,6 +354,11 @@ Global {B96E97F4-2DC8-45AC-ADF5-861D0D3073FC} = {A567C185-A429-418B-AFDE-6F1785BA4A77} {09C842CB-930C-4C7D-AD5F-E30DE4A55820} = {34D419E9-CCF1-4E48-9FA4-3AD4B86BEEB4} {2F5796A7-4AF8-4B78-928A-0A3A80752F9D} = {09C842CB-930C-4C7D-AD5F-E30DE4A55820} + {944FE7EF-9220-4CC6-BB20-CE517195B922} = {A567C185-A429-418B-AFDE-6F1785BA4A77} + {A1E878CB-ADF1-457A-9223-06F96ED8F7A6} = {99E234BC-997E-4E63-9F5C-3C3977543404} + {DF654202-0008-4CDE-B35E-018CEFD0FC68} = {A1E878CB-ADF1-457A-9223-06F96ED8F7A6} + {1467128C-90E4-4723-B5C7-9469B83F54A6} = {A1E878CB-ADF1-457A-9223-06F96ED8F7A6} + {E2F30496-19D8-46A8-9BC0-26936FFE70D2} = {A567C185-A429-418B-AFDE-6F1785BA4A77} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {929C0464-86D8-4F70-8835-0A5EAF930821} diff --git a/build/manifest.ps1 b/build/manifest.ps1 index cf7afbc72b4..07a8fca41f3 100644 --- a/build/manifest.ps1 +++ b/build/manifest.ps1 @@ -5,6 +5,7 @@ @{ Packages = @( + "Microsoft.Azure.Quantum.Client", "Microsoft.Quantum.CsharpGeneration", "Microsoft.Quantum.Development.Kit", "Microsoft.Quantum.QSharp.Core", @@ -13,6 +14,7 @@ "Microsoft.Quantum.Xunit" ); Assemblies = @( + ".\src\Azure\Azure.Quantum.Client\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Azure.Quantum.Client.dll", ".\src\simulation\CsharpGeneration\bin\$Env:BUILD_CONFIGURATION\netstandard2.1\Microsoft.Quantum.CsharpGeneration.dll", ".\src\simulation\CsharpGeneration.App\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.CsharpGeneration.App.dll", ".\src\simulation\CsharpGeneration.App\bin\$Env:BUILD_CONFIGURATION\netcoreapp3.1\Microsoft.Quantum.RoslynWrapper.dll", diff --git a/build/pack.ps1 b/build/pack.ps1 index 53c7d617f7f..7aa7227d8da 100644 --- a/build/pack.ps1 +++ b/build/pack.ps1 @@ -54,6 +54,7 @@ function Pack-Dotnet() { Write-Host "##[info]Using nuget to create packages" +Pack-Dotnet '../src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj' Pack-One '../src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj' '-IncludeReferencedProjects' Pack-Dotnet '../src/Simulation/Core/Microsoft.Quantum.Runtime.Core.csproj' Pack-Dotnet '../src/Simulation/QsharpCore/Microsoft.Quantum.QSharp.Core.csproj' diff --git a/build/test.ps1 b/build/test.ps1 index 95133997b06..4a879f1fb13 100644 --- a/build/test.ps1 +++ b/build/test.ps1 @@ -34,6 +34,8 @@ function Test-One { Test-One '../src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj' +Test-One '../src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj' + Test-One '../src/Simulation/RoslynWrapper.Tests/Tests.RoslynWrapper.fsproj' Test-One '../src/Simulation/QCTraceSimulator.Tests/Tests.Microsoft.Quantum.Simulation.QCTraceSimulatorRuntime.csproj' diff --git a/src/Azure/Azure.Quantum.Client.Test/Helpers/MockHelper.cs b/src/Azure/Azure.Quantum.Client.Test/Helpers/MockHelper.cs new file mode 100644 index 00000000000..29bc5a2b969 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client.Test/Helpers/MockHelper.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Moq.Protected; + +namespace Microsoft.Azure.Quantum.Test +{ + public static class MockHelper + { + internal static HttpRequestMessage RequestMessage { get; private set; } + + internal static HttpResponseMessage ResponseMessage { get; set; } + + public static HttpClient GetHttpClientMock() + { + Mock mock = new Mock(); + + mock.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .Returns((HttpRequestMessage request, CancellationToken token) => + { + RequestMessage = request; + return Task.FromResult(ResponseMessage); + }); + + return new HttpClient(mock.Object); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client.Test/Helpers/TestConstants.cs b/src/Azure/Azure.Quantum.Client.Test/Helpers/TestConstants.cs new file mode 100644 index 00000000000..6cc2c8aa96d --- /dev/null +++ b/src/Azure/Azure.Quantum.Client.Test/Helpers/TestConstants.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.Quantum.Test +{ + public static class TestConstants + { + public const string SubscriptionId = "sub1"; + public const string ResourceGroupName = "rg1"; + public const string WorkspaceName = "ws1"; + public const string ProviderId = "provider1"; + public const string Endpoint = "https://test"; + } +} diff --git a/src/Azure/Azure.Quantum.Client.Test/Microsoft.Azure.Quantum.Client.Test.csproj b/src/Azure/Azure.Quantum.Client.Test/Microsoft.Azure.Quantum.Client.Test.csproj new file mode 100644 index 00000000000..7965e696874 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client.Test/Microsoft.Azure.Quantum.Client.Test.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.1 + x64 + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + ..\Common\StyleCop.ruleset + Microsoft.Azure.Quantum.Test + + + + + + + + + + + diff --git a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs new file mode 100644 index 00000000000..57f1108f6dc --- /dev/null +++ b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using Microsoft.Azure.Quantum.Client; +using Microsoft.Azure.Quantum.Client.Models; +using Microsoft.Rest; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Quantum.Test +{ + [TestClass] + public class WorkspaceTest + { + [TestMethod] + public void SubmitJobTest() + { + string jobId = Guid.NewGuid().ToString(); + + // Craft response + SetJobResponseMessage(jobId); + + // Create Job + IWorkspace workspace = GetWorkspace(); + + JobDetails jobDetails = CreateJobDetails(jobId); + CloudJob job = new CloudJob(workspace, jobDetails); + CloudJob receivedJob; + + // -ve cases + try + { + jobDetails.ContainerUri = null; + receivedJob = workspace.SubmitJob(job); + Assert.Fail(); + } + catch (ValidationException) + { + jobDetails.ContainerUri = "https://uri"; + } + + try + { + jobDetails.ProviderId = null; + receivedJob = workspace.SubmitJob(job); + Assert.Fail(); + } + catch (ValidationException) + { + jobDetails.ProviderId = TestConstants.ProviderId; + } + + // Success + receivedJob = workspace.SubmitJob(job); + + // Validate request + ValidateJobRequestMessage(jobId, HttpMethod.Put); + + // Validate response + Assert.IsNotNull(receivedJob); + + Assert.IsNotNull(receivedJob.Workspace); + + Assert.AreEqual( + expected: jobId, + actual: receivedJob.Details.Id); + } + + [TestMethod] + public void GetJobTest() + { + string jobId = Guid.NewGuid().ToString(); + + // Craft response + SetJobResponseMessage(jobId); + + // Get Job + IWorkspace workspace = GetWorkspace(); + + CloudJob receivedJob = workspace.GetJob(jobId); + + // Validate request + ValidateJobRequestMessage(jobId, HttpMethod.Get); + + // Validate response + Assert.IsNotNull(receivedJob); + + Assert.IsNotNull(receivedJob.Workspace); + + Assert.AreEqual( + expected: jobId, + actual: receivedJob.Details.Id); + } + + [TestMethod] + public void CancelJobTest() + { + string jobId = Guid.NewGuid().ToString(); + + // Craft response + SetJobResponseMessage(jobId); + + // Cancel Job + IWorkspace workspace = GetWorkspace(); + + CloudJob receivedJob = workspace.CancelJob(jobId); + + // Validate request + ValidateJobRequestMessage(jobId, HttpMethod.Delete); + + // Validate response + Assert.IsNotNull(receivedJob); + + Assert.IsNotNull(receivedJob.Workspace); + + Assert.AreEqual( + expected: jobId, + actual: receivedJob.Details.Id); + + // Convenience method + CloudJob job = new CloudJob(workspace, CreateJobDetails(jobId)); + string newJobId = Guid.NewGuid().ToString(); + SetJobResponseMessage(newJobId); + + Assert.AreEqual( + jobId, + job.Details.Id); + + job.CancelAsync().Wait(); + + Assert.AreEqual( + newJobId, + job.Details.Id); + } + + [TestMethod] + public void ListJobsTest() + { + // Craft response + JobDetails jobDetails = new JobDetails + { + ProviderId = TestConstants.ProviderId, + }; + + Page page = new Page() + { + Items = new List { jobDetails }, + }; + + MockHelper.ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonConvert.SerializeObject(page)), + }; + + // Cancel Job + IWorkspace workspace = GetWorkspace(); + + List receivedJobs = workspace.ListJobs().ToList(); + + // Validate request + ValidateJobRequestMessage(null, HttpMethod.Get); + + // Validate response + Assert.IsNotNull(receivedJobs); + + Assert.IsNotNull(receivedJobs.Single().Workspace); + + Assert.AreEqual( + expected: jobDetails.ProviderId, + actual: receivedJobs.Single().Details.ProviderId); + } + + private static IWorkspace GetWorkspace() + { + return new Workspace( + subscriptionId: TestConstants.SubscriptionId, + resourceGroupName: TestConstants.ResourceGroupName, + workspaceName: TestConstants.WorkspaceName) + { + // Mock jobs client (only needed for unit tests) + JobsClient = new QuantumClient(MockHelper.GetHttpClientMock(), true) + { + SubscriptionId = TestConstants.SubscriptionId, + ResourceGroupName = TestConstants.ResourceGroupName, + WorkspaceName = TestConstants.WorkspaceName, + BaseUri = new Uri(TestConstants.Endpoint), + }.Jobs, + }; + } + + private static void SetJobResponseMessage(string jobId) + { + MockHelper.ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonConvert.SerializeObject(CreateJobDetails(jobId))), + }; + } + + private static JobDetails CreateJobDetails(string jobId) + { + return new JobDetails( + id: jobId, + containerUri: "https://uri", + inputDataFormat: "format1", + providerId: TestConstants.ProviderId, + target: "target"); + } + + private static void ValidateJobRequestMessage( + string jobId, + HttpMethod method) + { + var requestMessage = MockHelper.RequestMessage; + + // Url + string expectedUri = $"{TestConstants.Endpoint}/v1.0/subscriptions/{TestConstants.SubscriptionId}/resourceGroups/{TestConstants.ResourceGroupName}/providers/Microsoft.Quantum/workspaces/{TestConstants.WorkspaceName}/jobs/{jobId}"; + Assert.AreEqual( + expected: expectedUri.TrimEnd('/'), + actual: requestMessage.RequestUri.ToString()); + + // Method + Assert.AreEqual( + expected: method, + actual: requestMessage.Method); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Authentication/AuthorizationClientHandler.cs b/src/Azure/Azure.Quantum.Client/Authentication/AuthorizationClientHandler.cs new file mode 100644 index 00000000000..f81b20ca1aa --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/AuthorizationClientHandler.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Quantum.Authentication +{ + /// + /// Authorization client handler class. + /// + internal class AuthorizationClientHandler : HttpClientHandler + { + private const string AuthorizationHeaderName = "Authorization"; + private const string BearerScheme = "Bearer"; + + private readonly IAccessTokenProvider accessTokenProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The access token provider. + public AuthorizationClientHandler(IAccessTokenProvider accessTokenProvider) + { + this.accessTokenProvider = accessTokenProvider; + } + + /// + /// Creates an instance of based on the information provided in the as an operation that will not block. + /// + /// The HTTP request message. + /// A cancellation token to cancel the operation. + /// + /// The task object representing the asynchronous operation. + /// + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (!request.Headers.Contains(AuthorizationHeaderName)) + { + string accessToken = await this.accessTokenProvider.GetAccessTokenAsync(cancellationToken); + request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(BearerScheme, accessToken); + } + + return await base.SendAsync(request, cancellationToken); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Authentication/CustomAccessTokenProvider.cs b/src/Azure/Azure.Quantum.Client/Authentication/CustomAccessTokenProvider.cs new file mode 100644 index 00000000000..dfc447c1bc5 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/CustomAccessTokenProvider.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Linq; +using System.Net.Http; +using System.Security.Authentication; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum.Utility; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Extensions.Msal; + +namespace Microsoft.Azure.Quantum.Authentication +{ + /// + /// This class manually uses MSAL to get an access token. + /// It first tries to get silently, if that doesn't work, it tries to get interactively. + /// + /// + internal class CustomAccessTokenProvider : IAccessTokenProvider + { + private readonly LazyAsync applicationLazy; + private readonly string[] scopes; + private readonly string subscriptionId; + + /// + /// Initializes a new instance of the class. + /// + /// The Subscription Id of the account in use. + public CustomAccessTokenProvider(string subscriptionId) + { + static async Task GetSubscriptionTenantUri(string subscriptionId) + { + var uri = $"https://management.azure.com/subscriptions/{subscriptionId}?api-version=2018-01-01"; + try + { + static string GetTenantUriFromHeader(System.Net.Http.Headers.AuthenticationHeaderValue header) => + header + .Parameter + .Replace("Bearer ", string.Empty) + .Split(",") + .Select(part => part.Split("=")) + .ToDictionary(rg => rg[0], rg => rg[1])["authorization_uri"] + .Trim('\'', '"'); + + using var client = new HttpClient(); + var httpResult = await client.GetAsync(uri); + + return httpResult + .Headers + .WwwAuthenticate + .Select(GetTenantUriFromHeader) + .Single(); + } + catch (System.Exception ex) + { + throw new AuthenticationException("Unable to extract tenantUri!", ex); + } + } + + this.scopes = new string[] { Constants.Aad.Audience }; + this.subscriptionId = subscriptionId; + this.applicationLazy = + new LazyAsync(async () => + { + var application = PublicClientApplicationBuilder + .Create(Constants.Aad.ApplicationId) + .WithDefaultRedirectUri() + .WithAuthority(await GetSubscriptionTenantUri(subscriptionId)) + .Build(); + var cacheHelper = await CreateCacheHelperAsync(); + cacheHelper.RegisterCache(application.UserTokenCache); + return application; + }); + } + + /// + /// Tries to get access token silently, if didn't work, tries to get it interactively. + /// + /// The cancellation token. + /// A encapsulating the access token. + public async Task GetAccessTokenAsync(CancellationToken cancellationToken) + { + var application = applicationLazy.Value; + + try + { + var accounts = await application.GetAccountsAsync(); + + // Try silently first + var result = await application + .AcquireTokenSilent(scopes, accounts.FirstOrDefault()) + .ExecuteAsync(); + + return result.AccessToken; + } + catch (MsalUiRequiredException) + { + // Didn't work, perform interactive logging + var result = await application + .AcquireTokenInteractive(scopes) + .ExecuteAsync(); + + return result.AccessToken; + } + } + + private static async Task CreateCacheHelperAsync() + { + StorageCreationProperties storageProperties; + + storageProperties = new StorageCreationPropertiesBuilder( + Constants.Aad.CacheFileName, + MsalCacheHelper.UserRootDirectory, + Constants.Aad.ApplicationId) + .WithMacKeyChain( + Constants.Aad.KeyChainServiceName, + Constants.Aad.KeyChainAccountName) + .Build(); + + var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false); + + cacheHelper.VerifyPersistence(); + return cacheHelper; + } + } +} \ No newline at end of file diff --git a/src/Azure/Azure.Quantum.Client/Authentication/IAccessTokenProvider.cs b/src/Azure/Azure.Quantum.Client/Authentication/IAccessTokenProvider.cs new file mode 100644 index 00000000000..e72e4a440b8 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/IAccessTokenProvider.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Quantum.Authentication +{ + /// + /// Generic interface to retrieve access token. This is not exposed to the user. + /// + internal interface IAccessTokenProvider + { + /// + /// Gets the access token. + /// + /// The cancellation token. + /// Access token string + Task GetAccessTokenAsync(CancellationToken cancellationToken); + } +} diff --git a/src/Azure/Azure.Quantum.Client/Authentication/StaticAccessTokenProvider.cs b/src/Azure/Azure.Quantum.Client/Authentication/StaticAccessTokenProvider.cs new file mode 100644 index 00000000000..d44aa2e88ef --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/StaticAccessTokenProvider.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum.Utility; + +namespace Microsoft.Azure.Quantum.Authentication +{ + /// + /// This class accepts an access token string and provides it. + /// + /// + internal class StaticAccessTokenProvider : IAccessTokenProvider + { + private readonly string accessToken; + + /// + /// Initializes a new instance of the class. + /// + public StaticAccessTokenProvider(string accessToken) + { + Ensure.NotNullOrWhiteSpace(accessToken, nameof(accessToken)); + + this.accessToken = accessToken; + } + + /// + /// Returns the static access token. + /// + /// The cancellation token. + public Task GetAccessTokenAsync(CancellationToken cancellationToken) + { + return Task.FromResult(this.accessToken); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Authentication/TokenCredentialProvider.cs b/src/Azure/Azure.Quantum.Client/Authentication/TokenCredentialProvider.cs new file mode 100644 index 00000000000..dd4805f7fda --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Authentication/TokenCredentialProvider.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.Azure.Quantum.Utility; + +namespace Microsoft.Azure.Quantum.Authentication +{ + /// + /// A provider for TokenCredential + /// + /// + internal class TokenCredentialProvider : IAccessTokenProvider + { + private readonly TokenCredential tokenCredential; + private readonly string[] scopes; + + /// + /// Initializes a new instance of the class. + /// + /// The token credential. + public TokenCredentialProvider(TokenCredential tokenCredential) + { + this.tokenCredential = tokenCredential; + this.scopes = new string[] { Constants.Aad.Audience }; + } + + /// + /// Gets the access token. + /// + /// The cancellation token. + /// + /// Access token string + /// + public async Task GetAccessTokenAsync(CancellationToken cancellationToken) + { + AccessToken accessToken = await this.tokenCredential.GetTokenAsync(new TokenRequestContext(this.scopes), cancellationToken); + return accessToken.Token; + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Exceptions/AzureQuantumException.cs b/src/Azure/Azure.Quantum.Client/Exceptions/AzureQuantumException.cs new file mode 100644 index 00000000000..2a691d4bf75 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Exceptions/AzureQuantumException.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Azure.Quantum.Exceptions +{ + public class AzureQuantumException : Exception + { + public AzureQuantumException() + : base("An exception related to the Azure quantum occurred.") + { + } + + public AzureQuantumException(string message) + : base(message) + { + } + + public AzureQuantumException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs b/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs new file mode 100644 index 00000000000..9f3c54a7be8 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/JobManagement/CloudJob.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum.Client.Models; +using Microsoft.Azure.Quantum.Utility; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Azure.Quantum +{ + /// + /// Cloud job class. + /// + public class CloudJob : IQuantumMachineJob + { + /// + /// Initializes a new instance of the class. + /// + /// The workspace. + /// The job details. + public CloudJob(IWorkspace workspace, JobDetails jobDetails) + { + Ensure.NotNull(workspace, nameof(workspace)); + Ensure.NotNull(jobDetails, nameof(jobDetails)); + + Workspace = workspace; + Details = jobDetails; + } + + /// + /// Gets whether job execution failed. + /// + public bool Failed => !InProgress && + !Succeeded; + + /// + /// Gets the job id. + /// + public string Id => Details.Id; + + /// + /// Gets whether the job execution has completed. + /// + public bool InProgress => Status != "Cancelled" && + Status != "Failed" && + Status != "Succeeded"; + + /// + /// Gets the status of the submitted job. + /// + public string Status => Details.Status; + + /// + /// Gets whether the job execution completed successfully. + /// + public bool Succeeded => Status == "Succeeded"; + + /// + /// Gets an URI to access the job. + /// + public Uri Uri => throw new NotImplementedException(); + + /// + /// Gets the workspace. + /// + public IWorkspace Workspace { get; private set; } + + /// + /// Gets the job details. + /// + public JobDetails Details { get; private set; } + + /// + /// Refreshes the job. + /// + /// The cancellation token. + public async Task RefreshAsync(CancellationToken cancellationToken = default) + { + CloudJob job = (CloudJob)await this.Workspace.GetJobAsync(this.Details.Id, cancellationToken); + this.Details = job.Details; + } + + /// + /// Cancels the job. + /// + /// The cancellation token. + public async Task CancelAsync(CancellationToken cancellationToken = default) + { + CloudJob job = (CloudJob)await this.Workspace.CancelJobAsync(this.Details.Id, cancellationToken); + this.Details = job.Details; + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/IWorkspace.cs b/src/Azure/Azure.Quantum.Client/JobManagement/IWorkspace.cs new file mode 100644 index 00000000000..9ad313e8c8f --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/JobManagement/IWorkspace.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Azure.Quantum +{ + /// + /// IWorkspace interface. + /// + public interface IWorkspace + { + /// + /// Submits the job. + /// + /// The job definition. + /// The cancellation token. + /// The job response. + Task SubmitJobAsync( + CloudJob jobDefinition, + CancellationToken cancellationToken = default); + + /// + /// Cancels the job. + /// + /// The job identifier. + /// The cancellation token. + /// The job response. + Task CancelJobAsync( + string jobId, + CancellationToken cancellationToken = default); + + /// + /// Gets the job. + /// + /// The job identifier. + /// The cancellation token. + /// The job object. + Task GetJobAsync( + string jobId, + CancellationToken cancellationToken = default); + + /// + /// Lists the jobs. + /// + /// The cancellation token. + /// List of jobs + Task> ListJobsAsync( + CancellationToken cancellationToken = default); + } +} diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs new file mode 100644 index 00000000000..1f4fed67f10 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Microsoft.Azure.Quantum.Authentication; +using Microsoft.Azure.Quantum.Client; +using Microsoft.Azure.Quantum.Client.Models; +using Microsoft.Azure.Quantum.Storage; +using Microsoft.Azure.Quantum.Utility; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Azure.Quantum +{ + /// + /// Workspace class. + /// + /// + public class Workspace : IWorkspace + { + /// + /// Initializes a new instance of the class. + /// + /// The subscription identifier. + /// Name of the resource group. + /// Name of the workspace. + /// The token credential. + /// The base URI. + public Workspace( + string subscriptionId, + string resourceGroupName, + string workspaceName, + TokenCredential tokenCredential = null, + Uri baseUri = null) + : this( + subscriptionId, + resourceGroupName, + workspaceName, + tokenCredential == null ? null : new TokenCredentialProvider(tokenCredential), + baseUri) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The subscription identifier. + /// Name of the resource group. + /// Name of the workspace. + /// The access token. + /// The base URI. + public Workspace( + string subscriptionId, + string resourceGroupName, + string workspaceName, + string accessToken, + Uri baseUri = null) + : this( + subscriptionId, + resourceGroupName, + workspaceName, + new StaticAccessTokenProvider(accessToken), + baseUri) + { + } + + private Workspace( + string subscriptionId, + string resourceGroupName, + string workspaceName, + IAccessTokenProvider accessTokenProvider, + Uri baseUri = null) + { + Ensure.NotNullOrWhiteSpace(subscriptionId, nameof(subscriptionId)); + Ensure.NotNullOrWhiteSpace(resourceGroupName, nameof(resourceGroupName)); + Ensure.NotNullOrWhiteSpace(workspaceName, nameof(workspaceName)); + + accessTokenProvider = accessTokenProvider ?? new CustomAccessTokenProvider(subscriptionId); + + Ensure.NotNull(accessTokenProvider, nameof(accessTokenProvider)); + + this.JobsClient = new QuantumClient(new AuthorizationClientHandler(accessTokenProvider)) + { + BaseUri = baseUri ?? new Uri(Constants.DefaultBaseUri), + SubscriptionId = subscriptionId, + ResourceGroupName = resourceGroupName, + WorkspaceName = workspaceName, + }.Jobs; + } + + /// + /// Gets or sets the jobs client. + /// Internal only. + /// + /// + /// The jobs client. + /// + internal IJobsOperations JobsClient { get; set; } + + /// + /// Submits the job. + /// + /// The job definition. + /// The cancellation token. + /// + /// The job response. + /// + public async Task SubmitJobAsync( + CloudJob jobDefinition, + CancellationToken cancellationToken = default) + { + Ensure.NotNull(jobDefinition, nameof(jobDefinition)); + Ensure.NotNullOrWhiteSpace(jobDefinition.Details.Id, nameof(jobDefinition.Details.Id)); + + JobDetails jobDetails = await this.JobsClient.PutAsync( + jobId: jobDefinition.Details.Id, + jobDefinition: jobDefinition.Details, + cancellationToken: cancellationToken); + + return new CloudJob(this, jobDetails); + } + + /// + /// Cancels the job. + /// + /// The job identifier. + /// The cancellation token. + /// Cloud job. + public async Task CancelJobAsync(string jobId, CancellationToken cancellationToken = default) + { + Ensure.NotNullOrWhiteSpace(jobId, nameof(jobId)); + + JobDetails jobDetails = await this.JobsClient.DeleteAsync( + jobId: jobId, + cancellationToken: cancellationToken); + + return new CloudJob(this, jobDetails); + } + + /// + /// Gets the job. + /// + /// The job identifier. + /// The cancellation token. + /// + /// The job response. + /// + public async Task GetJobAsync(string jobId, CancellationToken cancellationToken = default) + { + Ensure.NotNullOrWhiteSpace(jobId, nameof(jobId)); + + JobDetails jobDetails = await this.JobsClient.GetAsync( + jobId: jobId, + cancellationToken: cancellationToken); + + return new CloudJob(this, jobDetails); + } + + /// + /// Lists the jobs. + /// + /// The cancellation token. + /// + /// List of jobs. + /// + public async Task> ListJobsAsync(CancellationToken cancellationToken = default) + { + var jobs = await this.JobsClient.ListAsync( + cancellationToken: cancellationToken); + + return jobs + .Select(details => new CloudJob(this, details)); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/WorkspaceExtensions.cs b/src/Azure/Azure.Quantum.Client/JobManagement/WorkspaceExtensions.cs new file mode 100644 index 00000000000..6d1730003b5 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/JobManagement/WorkspaceExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Azure.Quantum +{ + /// + /// Extension methods for Workspace. + /// + public static class WorkspaceExtensions + { + /// + /// Submits the job. + /// + /// The workspace. + /// The job definition. + /// The job response. + public static CloudJob SubmitJob( + this IWorkspace workspace, + CloudJob jobDefinition) + { + return workspace.SubmitJobAsync(jobDefinition).GetAwaiter().GetResult(); + } + + /// + /// Cancels the job. + /// + /// The workspace. + /// The job identifier. + /// The job response. + public static CloudJob CancelJob(this IWorkspace workspace, string jobId) + { + return workspace.CancelJobAsync(jobId).GetAwaiter().GetResult(); + } + + /// + /// Gets the job. + /// + /// The workspace. + /// The job identifier. + /// The job response. + public static CloudJob GetJob(this IWorkspace workspace, string jobId) + { + return workspace.GetJobAsync(jobId).GetAwaiter().GetResult(); + } + + /// + /// Lists the jobs. + /// + /// The workspace. + /// List of job identifiers. + public static IEnumerable ListJobs(this IWorkspace workspace) + { + return workspace.ListJobsAsync().GetAwaiter().GetResult(); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs new file mode 100644 index 00000000000..94ab65f2368 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Machine/QuantumMachineFactory.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Quantum.Runtime; + +namespace Microsoft.Azure.Quantum +{ + public static class QuantumMachineFactory + { + /// + /// Creates a quantum machine for job submission to an Azure Quantum workspace. + /// + /// The Azure Quantum workspace. + /// The execution target for job submission. + /// The connection string for the Azure storage account. + /// A quantum machine for job submission targeting targetName. + public static IQuantumMachine? CreateMachine(IWorkspace workspace, string targetName, string storageAccountConnectionString) + { + var machineName = + targetName is null + ? null + : targetName.StartsWith("ionq.") + ? "Microsoft.Quantum.Providers.IonQ.Targets.IonQQuantumMachine, Microsoft.Quantum.Providers.IonQ" + : targetName.StartsWith("honeywell.") + ? "Microsoft.Quantum.Providers.Honeywell.Targets.HoneywellQuantumMachine, Microsoft.Quantum.Providers.Honeywell" + : null; + + Type machineType = null; + if (machineName != null) + { + // First try to load the signed assembly with the correct version, then try the unsigned one. + try + { + machineType = Type.GetType($"{machineName}, Version={typeof(IWorkspace).Assembly.GetName().Version}, Culture=neutral, PublicKeyToken=40866b40fd95c7f5"); + } + catch + { + machineType = null; + } + + machineType ??= Type.GetType(machineName, throwOnError: true); + } + + return machineType is null + ? null + : (IQuantumMachine)Activator.CreateInstance( + machineType, + targetName, + storageAccountConnectionString, + workspace); + } + } +} \ No newline at end of file diff --git a/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj b/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj new file mode 100644 index 00000000000..d4b6c807b9e --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Microsoft.Azure.Quantum.Client.csproj @@ -0,0 +1,49 @@ + + + + + + netstandard2.1 + x64 + Client library for Azure Quantum. + + + + https://docs.microsoft.com/quantum/ + See: https://docs.microsoft.com/quantum/relnotes/ + MIT + https://github.com/microsoft/qsharp-runtime + Azure Quantum Q# Qsharp + https://secure.gravatar.com/avatar/bd1f02955b2853ba0a3b1cdc2434e8ec.png + true + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + ..\Common\StyleCop.ruleset + + + + + + + + + + + diff --git a/src/Azure/Azure.Quantum.Client/Properties/AssemblyInfo.cs b/src/Azure/Azure.Quantum.Client/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..3f533030058 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Azure.Quantum.Client.Test" + DelaySign.PUBLIC_KEY)] \ No newline at end of file diff --git a/src/Azure/Azure.Quantum.Client/Storage/IJobStorageHelper.cs b/src/Azure/Azure.Quantum.Client/Storage/IJobStorageHelper.cs new file mode 100644 index 00000000000..c5a47217a7b --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Storage/IJobStorageHelper.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Bond; + +namespace Microsoft.Azure.Quantum.Storage +{ + /// + /// Job storage helper. + /// + public interface IJobStorageHelper + { + /// + /// Gets the underlying storage helper. + /// + IStorageHelper StorageHelper { get; } + + /// + /// Uploads the job input. + /// + /// The job id. + /// The input. + /// Serialization protocol of the input to upload. + /// The cancellation token. + /// Container uri + Input uri. + Task<(string containerUri, string inputUri)> UploadJobInputAsync( + string jobId, + Stream input, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default); + + /// + /// Uploads the job program output mapping. + /// + /// The job id. + /// The job program output mapping. + /// Serialization protocol of the mapping to upload. + /// The cancellation token. + /// Container uri + Mapping uri. + Task<(string containerUri, string mappingUri)> UploadJobMappingAsync( + string jobId, + Stream mapping, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default); + + /// + /// Downloads the job's execution output. + /// + /// The job id. + /// The destination stream. + /// The cancellation token. + /// Serialization protocol of the downloaded execution output. + Task DownloadJobOutputAsync( + string jobId, + Stream destination, + CancellationToken cancellationToken = default); + } +} diff --git a/src/Azure/Azure.Quantum.Client/Storage/IStorageHelper.cs b/src/Azure/Azure.Quantum.Client/Storage/IStorageHelper.cs new file mode 100644 index 00000000000..6b8f937890f --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Storage/IStorageHelper.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Bond; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace Microsoft.Azure.Quantum.Storage +{ + public interface IStorageHelper + { + /// + /// Downloads the BLOB. + /// + /// Name of the container. + /// Name of the BLOB. + /// The destination. + /// The cancellation token. + /// Serialization protocol of the downloaded BLOB. + Task DownloadBlobAsync( + string containerName, + string blobName, + Stream destination, + CancellationToken cancellationToken = default); + + /// + /// Uploads the BLOB. + /// + /// Name of the container. + /// Name of the BLOB. + /// The input. + /// Serialization protocol of the BLOB to upload. + /// The cancellation token. + /// async task. + Task UploadBlobAsync( + string containerName, + string blobName, + Stream input, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default); + + /// + /// Gets the BLOB sas URI. + /// + /// Name of the container. + /// Name of the BLOB. + /// The expiry interval. + /// The permissions. + /// Blob uri. + string GetBlobSasUri( + string containerName, + string blobName, + TimeSpan expiryInterval, + SharedAccessBlobPermissions permissions); + + /// + /// Gets the BLOB container sas URI. + /// + /// Name of the container. + /// The expiry interval. + /// The permissions. + /// Container uri. + string GetBlobContainerSasUri( + string containerName, + TimeSpan expiryInterval, + SharedAccessBlobPermissions permissions); + } +} diff --git a/src/Azure/Azure.Quantum.Client/Storage/JobStorageHelper.cs b/src/Azure/Azure.Quantum.Client/Storage/JobStorageHelper.cs new file mode 100644 index 00000000000..17fc10a7103 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Storage/JobStorageHelper.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Bond; +using Microsoft.Azure.Quantum.Utility; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace Microsoft.Azure.Quantum.Storage +{ + public class JobStorageHelper : IJobStorageHelper + { + private readonly TimeSpan expiryInterval; + + /// + /// Initializes a new instance of the class. + /// + /// The connection string. + public JobStorageHelper(string connectionString) + { + this.StorageHelper = new StorageHelper(connectionString); + this.expiryInterval = TimeSpan.FromDays(Constants.Storage.ExpiryIntervalInDays); + } + + /// + /// Gets the underlying storage helper. + /// + public IStorageHelper StorageHelper { get; } + + /// + /// Uploads the job input. + /// + /// The job id. + /// The input. + /// Serialization protocol of the input to upload. + /// The cancellation token. + /// + /// Container uri + Input uri. + /// + public async Task<(string containerUri, string inputUri)> UploadJobInputAsync( + string jobId, + Stream input, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default) + { + string containerName = GetContainerName(jobId); + await this.StorageHelper.UploadBlobAsync( + containerName, + Constants.Storage.InputBlobName, + input, + protocol, + cancellationToken); + + string containerUri = this.StorageHelper.GetBlobContainerSasUri( + containerName, + this.expiryInterval, + SharedAccessBlobPermissions.Create | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read); + + string inputUri = this.StorageHelper.GetBlobSasUri( + containerName, + Constants.Storage.InputBlobName, + this.expiryInterval, + SharedAccessBlobPermissions.Read); + + return (containerUri, inputUri); + } + + /// + /// Uploads the job program output mapping. + /// + /// The job id. + /// The job program output mapping. + /// Serialization protocol of the mapping to upload. + /// The cancellation token. + /// Container uri + Mapping uri. + public async Task<(string containerUri, string mappingUri)> UploadJobMappingAsync( + string jobId, + Stream mapping, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default) + { + string containerName = GetContainerName(jobId); + await this.StorageHelper.UploadBlobAsync( + containerName, + Constants.Storage.MappingBlobName, + mapping, + protocol, + cancellationToken); + + string containerUri = this.StorageHelper.GetBlobContainerSasUri( + containerName, + this.expiryInterval, + SharedAccessBlobPermissions.Create | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read); + + string mappingUri = this.StorageHelper.GetBlobSasUri( + containerName, + Constants.Storage.MappingBlobName, + this.expiryInterval, + SharedAccessBlobPermissions.Read); + + return (containerUri, mappingUri); + } + + /// + /// Downloads the job's execution output. + /// + /// The job id. + /// The destination stream. + /// The cancellation token. + /// Serialization protocol of the downloaded execution output. + public Task DownloadJobOutputAsync( + string jobId, + Stream destination, + CancellationToken cancellationToken = default) + { + string containerName = GetContainerName(jobId); + return this.StorageHelper.DownloadBlobAsync( + containerName, + "rawOutputData", // TODO: 14643 + destination, + cancellationToken); + } + + private static string GetContainerName(string jobId) + { + return Constants.Storage.ContainerNamePrefix + jobId.ToLowerInvariant(); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Storage/StorageHelper.cs b/src/Azure/Azure.Quantum.Client/Storage/StorageHelper.cs new file mode 100644 index 00000000000..93d6edf1eb4 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Storage/StorageHelper.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using Bond; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Blob; + +namespace Microsoft.Azure.Quantum.Storage +{ + internal class StorageHelper : IStorageHelper + { + private readonly string connectionString; + private readonly CloudStorageAccount storageAccount; + + /// + /// Initializes a new instance of the class. + /// + /// The connection string. + public StorageHelper(string connectionString) + { + this.connectionString = connectionString; + this.storageAccount = CloudStorageAccount.Parse(connectionString); + } + + /// + /// Downloads the BLOB. + /// + /// Name of the container. + /// Name of the BLOB. + /// The destination. + /// The cancellation token. + /// Serialization protocol of the downloaded BLOB. + public async Task DownloadBlobAsync( + string containerName, + string blobName, + Stream destination, + CancellationToken cancellationToken = default) + { + BlobClient blob = await this.GetBlobClient(containerName, blobName, false, cancellationToken); + await blob.DownloadToAsync(destination, cancellationToken); + return ProtocolType.COMPACT_PROTOCOL; + } + + /// + /// Uploads the BLOB. + /// + /// Name of the container. + /// Name of the BLOB. + /// The input. + /// Serialization protocol of the BLOB to upload. + /// The cancellation token. + /// Async task. + public async Task UploadBlobAsync( + string containerName, + string blobName, + Stream input, + ProtocolType protocol = ProtocolType.COMPACT_PROTOCOL, + CancellationToken cancellationToken = default) + { + BlobClient blob = await this.GetBlobClient(containerName, blobName, true, cancellationToken); + await blob.UploadAsync(input, overwrite: true, cancellationToken); + } + + /// + /// Gets the BLOB sas URI. + /// + /// Name of the container. + /// Name of the BLOB. + /// The expiry interval. + /// The permissions. + /// Blob uri. + public string GetBlobSasUri( + string containerName, + string blobName, + TimeSpan expiryInterval, + SharedAccessBlobPermissions permissions) + { + SharedAccessBlobPolicy adHocSAS = CreateSharedAccessBlobPolicy(expiryInterval, permissions); + + CloudBlob blob = this.storageAccount + .CreateCloudBlobClient() + .GetContainerReference(containerName) + .GetBlobReference(blobName); + + return blob.Uri + blob.GetSharedAccessSignature(adHocSAS); + } + + /// + /// Gets the BLOB container sas URI. + /// + /// Name of the container. + /// The expiry interval. + /// The permissions. + /// Container uri. + public string GetBlobContainerSasUri( + string containerName, + TimeSpan expiryInterval, + SharedAccessBlobPermissions permissions) + { + SharedAccessBlobPolicy adHocPolicy = CreateSharedAccessBlobPolicy(expiryInterval, permissions); + + // Generate the shared access signature on the container, setting the constraints directly on the signature. + CloudBlobContainer container = this.storageAccount.CreateCloudBlobClient().GetContainerReference(containerName); + return container.Uri + container.GetSharedAccessSignature(adHocPolicy, null); + } + + private async Task GetBlobClient( + string containerName, + string blobName, + bool createContainer, + CancellationToken cancellationToken) + { + BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString); + BlobContainerClient blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName); + + if (createContainer) + { + await blobContainerClient.CreateIfNotExistsAsync(PublicAccessType.Blob, cancellationToken: cancellationToken); + } + + return blobContainerClient.GetBlobClient(blobName); + } + + private static SharedAccessBlobPolicy CreateSharedAccessBlobPolicy( + TimeSpan expiryInterval, + SharedAccessBlobPermissions permissions) + { + return new SharedAccessBlobPolicy() + { + // When the start time for the SAS is omitted, the start time is assumed to be the time when the storage service receives the request. + // Omitting the start time for a SAS that is effective immediately helps to avoid clock skew. + SharedAccessExpiryTime = DateTime.UtcNow.Add(expiryInterval), + Permissions = permissions, + }; + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Utility/Constants.cs b/src/Azure/Azure.Quantum.Client/Utility/Constants.cs new file mode 100644 index 00000000000..4a20a757ad7 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Utility/Constants.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Azure.Quantum.Utility +{ + internal static class Constants + { + internal const string DefaultBaseUri = "https://app-jobscheduler-prod.azurewebsites.net/"; + + internal static class Aad + { + internal const string ApplicationId = "84ba0947-6c53-4dd2-9ca9-b3694761521b"; + + internal const string Audience = "https://quantum.microsoft.com/Jobs.ReadWrite"; + + // Cache settings + internal const string CacheFileName = "msal_cache.dat"; + + // Mac Keychain settings + internal const string KeyChainServiceName = "msal_service"; + internal const string KeyChainAccountName = "msal_account"; + } + + internal static class Storage + { + internal const string ContainerNamePrefix = "quantum-job-"; + internal const string InputBlobName = "inputData"; + internal const string MappingBlobName = "mappingData"; + internal const string OutputBlobName = "outputData"; + internal const int ExpiryIntervalInDays = 14; + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Utility/Ensure.cs b/src/Azure/Azure.Quantum.Client/Utility/Ensure.cs new file mode 100644 index 00000000000..fff4c9bb7c9 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Utility/Ensure.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.Azure.Quantum.Utility +{ + /// + /// Validation class. + /// + internal static class Ensure + { + /// + /// Ensures not null or whitespace. + /// + /// The value. + /// The name. + public static void NotNullOrWhiteSpace(string value, string name) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(name); + } + } + + /// + /// Ensures not null. + /// + /// The value. + /// The name. + public static void NotNull(object value, string name) + { + if (value == null) + { + throw new ArgumentNullException(name); + } + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/Utility/LazyAsync.cs b/src/Azure/Azure.Quantum.Client/Utility/LazyAsync.cs new file mode 100644 index 00000000000..99f27d02124 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/Utility/LazyAsync.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Based on https://gist.github.com/johnazariah/ab269f7e005d538ed706b7a9cdb15bf1 + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Quantum.Utility +{ + internal sealed class LazyAsync + { + private readonly Lazy> instance; + private readonly Lazy valueL; + + /// + /// Constructor for use with synchronous factories + /// + public LazyAsync(Func synchronousFactory) + : this(new Lazy>(() => Task.Run(synchronousFactory))) + { + } + + /// + /// Constructor for use with asynchronous factories + /// + public LazyAsync(Func> asynchronousFactory) + : this(new Lazy>(() => asynchronousFactory())) + { + } + + // private constructor which sets both fields + private LazyAsync(Lazy> instance) + { + this.instance = instance; + this.valueL = new Lazy(() => this.instance.Value.GetAwaiter().GetResult()); + } + + public T Value => valueL.Value; + + public TaskAwaiter GetAwaiter() => instance.Value.GetAwaiter(); + } +} \ No newline at end of file diff --git a/src/Azure/Azure.Quantum.Client/generated/IJobsOperations.cs b/src/Azure/Azure.Quantum.Client/generated/IJobsOperations.cs new file mode 100644 index 00000000000..74fd4d11463 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/IJobsOperations.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using System.Collections; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + /// + /// JobsOperations operations. + /// + public partial interface IJobsOperations + { + /// + /// List jobs. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task>> ListWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Get job by id + /// + /// + /// Id of the job. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task> GetWithHttpMessagesAsync(string jobId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Create a job. + /// + /// + /// Id of the job. + /// + /// + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task> PutWithHttpMessagesAsync(string jobId, JobDetails jobDefinition, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Delete a job. + /// + /// + /// Id of the job. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task> DeleteWithHttpMessagesAsync(string jobId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// List jobs. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task>> ListNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/IProvidersOperations.cs b/src/Azure/Azure.Quantum.Client/generated/IProvidersOperations.cs new file mode 100644 index 00000000000..2cc635ca7c8 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/IProvidersOperations.cs @@ -0,0 +1,68 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using System.Collections; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + /// + /// ProvidersOperations operations. + /// + public partial interface IProvidersOperations + { + /// + /// Get provider status. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task>> GetStatusWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Get provider status. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// The headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + Task>> GetStatusNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/IQuantumClient.cs b/src/Azure/Azure.Quantum.Client/generated/IQuantumClient.cs new file mode 100644 index 00000000000..f4ae50f1a37 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/IQuantumClient.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using Newtonsoft.Json; + + /// + /// Azure Quantum REST API client + /// + public partial interface IQuantumClient : System.IDisposable + { + /// + /// The base URI of the service. + /// + System.Uri BaseUri { get; set; } + + /// + /// Gets or sets json serialization settings. + /// + JsonSerializerSettings SerializationSettings { get; } + + /// + /// Gets or sets json deserialization settings. + /// + JsonSerializerSettings DeserializationSettings { get; } + + /// + /// Credentials needed for the client to connect to Azure. + /// + ServiceClientCredentials Credentials { get; } + + /// + /// The Azure subscription ID. This is a GUID-formatted string (e.g. + /// 00000000-0000-0000-0000-000000000000) + /// + string SubscriptionId { get; set; } + + /// + /// Name of an Azure resource group. + /// + string ResourceGroupName { get; set; } + + /// + /// Name of the workspace. + /// + string WorkspaceName { get; set; } + + /// + /// The preferred language for the response. + /// + string AcceptLanguage { get; set; } + + /// + /// The retry timeout in seconds for Long Running Operations. Default + /// value is 30. + /// + int? LongRunningOperationRetryTimeout { get; set; } + + /// + /// Whether a unique x-ms-client-request-id should be generated. When + /// set to true a unique x-ms-client-request-id value is generated and + /// included in each request. Default is true. + /// + bool? GenerateClientRequestId { get; set; } + + + /// + /// Gets the IJobsOperations. + /// + IJobsOperations Jobs { get; } + + /// + /// Gets the IProvidersOperations. + /// + IProvidersOperations Providers { get; } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/JobsOperations.cs b/src/Azure/Azure.Quantum.Client/generated/JobsOperations.cs new file mode 100644 index 00000000000..81697920294 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/JobsOperations.cs @@ -0,0 +1,994 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + /// + /// JobsOperations operations. + /// + internal partial class JobsOperations : IServiceOperations, IJobsOperations + { + /// + /// Initializes a new instance of the JobsOperations class. + /// + /// + /// Reference to the service client. + /// + /// + /// Thrown when a required parameter is null + /// + internal JobsOperations(QuantumClient client) + { + if (client == null) + { + throw new System.ArgumentNullException("client"); + } + Client = client; + } + + /// + /// Gets a reference to the QuantumClient + /// + public QuantumClient Client { get; private set; } + + /// + /// List jobs. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task>> ListWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (Client.ResourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ResourceGroupName"); + } + if (Client.WorkspaceName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.WorkspaceName"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "List", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobs").ToString(); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(Client.ResourceGroupName)); + _url = _url.Replace("{workspaceName}", System.Uri.EscapeDataString(Client.WorkspaceName)); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new CloudException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + CloudError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex = new CloudException(_errorBody.Message); + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + ex.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse>(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject>(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + /// + /// Get job by id + /// + /// + /// Id of the job. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> GetWithHttpMessagesAsync(string jobId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (Client.ResourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ResourceGroupName"); + } + if (Client.WorkspaceName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.WorkspaceName"); + } + if (jobId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "jobId"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("jobId", jobId); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Get", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobs/{jobId}").ToString(); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(Client.ResourceGroupName)); + _url = _url.Replace("{workspaceName}", System.Uri.EscapeDataString(Client.WorkspaceName)); + _url = _url.Replace("{jobId}", System.Uri.EscapeDataString(jobId)); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new RestErrorException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + RestError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + /// + /// Create a job. + /// + /// + /// Id of the job. + /// + /// + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> PutWithHttpMessagesAsync(string jobId, JobDetails jobDefinition, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (Client.ResourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ResourceGroupName"); + } + if (Client.WorkspaceName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.WorkspaceName"); + } + if (jobId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "jobId"); + } + if (jobDefinition == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "jobDefinition"); + } + if (jobDefinition != null) + { + jobDefinition.Validate(); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("jobId", jobId); + tracingParameters.Add("jobDefinition", jobDefinition); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Put", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobs/{jobId}").ToString(); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(Client.ResourceGroupName)); + _url = _url.Replace("{workspaceName}", System.Uri.EscapeDataString(Client.WorkspaceName)); + _url = _url.Replace("{jobId}", System.Uri.EscapeDataString(jobId)); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("PUT"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + if(jobDefinition != null) + { + _requestContent = Microsoft.Rest.Serialization.SafeJsonConvert.SerializeObject(jobDefinition, Client.SerializationSettings); + _httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8); + _httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + } + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200 && (int)_statusCode != 201) + { + var ex = new RestErrorException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + RestError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + // Deserialize Response + if ((int)_statusCode == 201) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + /// + /// Delete a job. + /// + /// + /// Id of the job. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task> DeleteWithHttpMessagesAsync(string jobId, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (Client.ResourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ResourceGroupName"); + } + if (Client.WorkspaceName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.WorkspaceName"); + } + if (jobId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "jobId"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("jobId", jobId); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "Delete", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobs/{jobId}").ToString(); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(Client.ResourceGroupName)); + _url = _url.Replace("{workspaceName}", System.Uri.EscapeDataString(Client.WorkspaceName)); + _url = _url.Replace("{jobId}", System.Uri.EscapeDataString(jobId)); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("DELETE"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200 && (int)_statusCode != 204) + { + var ex = new RestErrorException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + RestError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + /// + /// List jobs. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task>> ListNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (nextPageLink == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "nextPageLink"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("nextPageLink", nextPageLink); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "ListNext", tracingParameters); + } + // Construct URL + string _url = "{nextLink}"; + _url = _url.Replace("{nextLink}", nextPageLink); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new CloudException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + CloudError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex = new CloudException(_errorBody.Message); + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + ex.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse>(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject>(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/JobsOperationsExtensions.cs b/src/Azure/Azure.Quantum.Client/generated/JobsOperationsExtensions.cs new file mode 100644 index 00000000000..bfec9488a12 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/JobsOperationsExtensions.cs @@ -0,0 +1,193 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Extension methods for JobsOperations. + /// + public static partial class JobsOperationsExtensions + { + /// + /// List jobs. + /// + /// + /// The operations group for this extension method. + /// + public static IPage List(this IJobsOperations operations) + { + return operations.ListAsync().GetAwaiter().GetResult(); + } + + /// + /// List jobs. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The cancellation token. + /// + public static async Task> ListAsync(this IJobsOperations operations, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.ListWithHttpMessagesAsync(null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + /// + /// Get job by id + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + public static JobDetails Get(this IJobsOperations operations, string jobId) + { + return operations.GetAsync(jobId).GetAwaiter().GetResult(); + } + + /// + /// Get job by id + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + /// + /// The cancellation token. + /// + public static async Task GetAsync(this IJobsOperations operations, string jobId, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.GetWithHttpMessagesAsync(jobId, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + /// + /// Create a job. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + /// + /// + public static JobDetails Put(this IJobsOperations operations, string jobId, JobDetails jobDefinition) + { + return operations.PutAsync(jobId, jobDefinition).GetAwaiter().GetResult(); + } + + /// + /// Create a job. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + /// + /// + /// + /// The cancellation token. + /// + public static async Task PutAsync(this IJobsOperations operations, string jobId, JobDetails jobDefinition, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.PutWithHttpMessagesAsync(jobId, jobDefinition, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + /// + /// Delete a job. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + public static JobDetails Delete(this IJobsOperations operations, string jobId) + { + return operations.DeleteAsync(jobId).GetAwaiter().GetResult(); + } + + /// + /// Delete a job. + /// + /// + /// The operations group for this extension method. + /// + /// + /// Id of the job. + /// + /// + /// The cancellation token. + /// + public static async Task DeleteAsync(this IJobsOperations operations, string jobId, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.DeleteWithHttpMessagesAsync(jobId, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + /// + /// List jobs. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + public static IPage ListNext(this IJobsOperations operations, string nextPageLink) + { + return operations.ListNextAsync(nextPageLink).GetAwaiter().GetResult(); + } + + /// + /// List jobs. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// The cancellation token. + /// + public static async Task> ListNextAsync(this IJobsOperations operations, string nextPageLink, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.ListNextWithHttpMessagesAsync(nextPageLink, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/JobDetails.cs b/src/Azure/Azure.Quantum.Client/generated/Models/JobDetails.cs new file mode 100644 index 00000000000..82627baecd0 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/JobDetails.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Microsoft.Rest; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Job details. + /// + public partial class JobDetails + { + /// + /// Initializes a new instance of the JobDetails class. + /// + public JobDetails() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the JobDetails class. + /// + /// The blob container SAS uri, the + /// container is used to host job data. + /// The format of the input data. + /// The unique identifier for the + /// provider. + /// The target identifier to run the job. + /// The job id. + /// The job name. Is not required for the name to be + /// unique and it's only used for display purposes. + /// The input blob SAS uri, if specified, it + /// will override the default input blob in the container. + /// The input parameters for the job. JSON + /// object used by the target solver. It is expected that the size of + /// this object is small and only used to specify parameters for the + /// execution target, not the input data. + /// The job metadata. Metadata provides client + /// the ability to store client-specific information + /// The output blob SAS uri. When a job + /// finishes successfully, results will be uploaded to this + /// blob. + /// The format of the output + /// data. + /// The job status. Possible values include: + /// 'Waiting', 'Executing', 'Succeeded', 'Failed', 'Cancelled' + /// The creation time of the job. + /// The time when the job began + /// execution. + /// The time when the job finished + /// execution. + /// The time when a job was successfully + /// cancelled. + /// The error data for the job. This is + /// expected only when Status 'Failed'. + public JobDetails(string containerUri, string inputDataFormat, string providerId, string target, string id = default(string), string name = default(string), string inputDataUri = default(string), object inputParams = default(object), IDictionary metadata = default(IDictionary), string outputDataUri = default(string), string outputDataFormat = default(string), string status = default(string), string creationTime = default(string), string beginExecutionTime = default(string), string endExecutionTime = default(string), string cancellationTime = default(string), RestError errorData = default(RestError)) + { + Id = id; + Name = name; + ContainerUri = containerUri; + InputDataUri = inputDataUri; + InputDataFormat = inputDataFormat; + InputParams = inputParams; + ProviderId = providerId; + Target = target; + Metadata = metadata; + OutputDataUri = outputDataUri; + OutputDataFormat = outputDataFormat; + Status = status; + CreationTime = creationTime; + BeginExecutionTime = beginExecutionTime; + EndExecutionTime = endExecutionTime; + CancellationTime = cancellationTime; + ErrorData = errorData; + CustomInit(); + } + + /// + /// An initialization method that performs custom operations like setting defaults + /// + partial void CustomInit(); + + /// + /// Gets or sets the job id. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; set; } + + /// + /// Gets or sets the job name. Is not required for the name to be + /// unique and it's only used for display purposes. + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the blob container SAS uri, the container is used to + /// host job data. + /// + [JsonProperty(PropertyName = "containerUri")] + public string ContainerUri { get; set; } + + /// + /// Gets or sets the input blob SAS uri, if specified, it will override + /// the default input blob in the container. + /// + [JsonProperty(PropertyName = "inputDataUri")] + public string InputDataUri { get; set; } + + /// + /// Gets or sets the format of the input data. + /// + [JsonProperty(PropertyName = "inputDataFormat")] + public string InputDataFormat { get; set; } + + /// + /// Gets or sets the input parameters for the job. JSON object used by + /// the target solver. It is expected that the size of this object is + /// small and only used to specify parameters for the execution target, + /// not the input data. + /// + [JsonProperty(PropertyName = "inputParams")] + public object InputParams { get; set; } + + /// + /// Gets or sets the unique identifier for the provider. + /// + [JsonProperty(PropertyName = "providerId")] + public string ProviderId { get; set; } + + /// + /// Gets or sets the target identifier to run the job. + /// + [JsonProperty(PropertyName = "target")] + public string Target { get; set; } + + /// + /// Gets or sets the job metadata. Metadata provides client the ability + /// to store client-specific information + /// + [JsonProperty(PropertyName = "metadata")] + public IDictionary Metadata { get; set; } + + /// + /// Gets or sets the output blob SAS uri. When a job finishes + /// successfully, results will be uploaded to this blob. + /// + [JsonProperty(PropertyName = "outputDataUri")] + public string OutputDataUri { get; set; } + + /// + /// Gets or sets the format of the output data. + /// + [JsonProperty(PropertyName = "outputDataFormat")] + public string OutputDataFormat { get; set; } + + /// + /// Gets the job status. Possible values include: 'Waiting', + /// 'Executing', 'Succeeded', 'Failed', 'Cancelled' + /// + [JsonProperty(PropertyName = "status")] + public string Status { get; private set; } + + /// + /// Gets the creation time of the job. + /// + [JsonProperty(PropertyName = "creationTime")] + public string CreationTime { get; private set; } + + /// + /// Gets the time when the job began execution. + /// + [JsonProperty(PropertyName = "beginExecutionTime")] + public string BeginExecutionTime { get; private set; } + + /// + /// Gets the time when the job finished execution. + /// + [JsonProperty(PropertyName = "endExecutionTime")] + public string EndExecutionTime { get; private set; } + + /// + /// Gets the time when a job was successfully cancelled. + /// + [JsonProperty(PropertyName = "cancellationTime")] + public string CancellationTime { get; private set; } + + /// + /// Gets the error data for the job. This is expected only when Status + /// 'Failed'. + /// + [JsonProperty(PropertyName = "errorData")] + public RestError ErrorData { get; private set; } + + /// + /// Validate the object. + /// + /// + /// Thrown if validation fails + /// + public virtual void Validate() + { + if (ContainerUri == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "ContainerUri"); + } + if (InputDataFormat == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "InputDataFormat"); + } + if (ProviderId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "ProviderId"); + } + if (Target == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Target"); + } + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/Page.cs b/src/Azure/Azure.Quantum.Client/generated/Models/Page.cs new file mode 100644 index 00000000000..7ab5d320626 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/Page.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + + /// + /// Defines a page in Azure responses. + /// + /// Type of the page content items + [JsonObject] + public class Page : IPage + { + /// + /// Gets the link to the next page. + /// + [JsonProperty("nextLink")] + public string NextPageLink { get; private set; } + + [JsonProperty("value")] + internal IList Items { get; set; } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// A an enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return Items == null ? System.Linq.Enumerable.Empty().GetEnumerator() : Items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// A an enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/ProviderStatus.cs b/src/Azure/Azure.Quantum.Client/generated/Models/ProviderStatus.cs new file mode 100644 index 00000000000..43baf39b445 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/ProviderStatus.cs @@ -0,0 +1,69 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Providers status. + /// + public partial class ProviderStatus + { + /// + /// Initializes a new instance of the ProviderStatus class. + /// + public ProviderStatus() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the ProviderStatus class. + /// + /// Provider id. + /// Provider availability. Possible + /// values include: 'Available', 'Degraded', 'Unavailable' + public ProviderStatus(string id = default(string), string currentAvailability = default(string), IList targets = default(IList)) + { + Id = id; + CurrentAvailability = currentAvailability; + Targets = targets; + CustomInit(); + } + + /// + /// An initialization method that performs custom operations like setting defaults + /// + partial void CustomInit(); + + /// + /// Gets provider id. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; private set; } + + /// + /// Gets provider availability. Possible values include: 'Available', + /// 'Degraded', 'Unavailable' + /// + [JsonProperty(PropertyName = "currentAvailability")] + public string CurrentAvailability { get; private set; } + + /// + /// + [JsonProperty(PropertyName = "targets")] + public IList Targets { get; private set; } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/RestError.cs b/src/Azure/Azure.Quantum.Client/generated/Models/RestError.cs new file mode 100644 index 00000000000..4917d841e8e --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/RestError.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Newtonsoft.Json; + using System.Linq; + + /// + /// An Error response. + /// + [JsonTransformation] + public partial class RestError + { + /// + /// Initializes a new instance of the RestError class. + /// + public RestError() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the RestError class. + /// + /// An identifier for the error. Codes are invariant + /// and are intended to be consumed programmatically. + /// A message describing the error, intended to + /// be suitable for displaying in a user interface. + public RestError(string code = default(string), string message = default(string)) + { + Code = code; + Message = message; + CustomInit(); + } + + /// + /// An initialization method that performs custom operations like setting defaults + /// + partial void CustomInit(); + + /// + /// Gets or sets an identifier for the error. Codes are invariant and + /// are intended to be consumed programmatically. + /// + [JsonProperty(PropertyName = "error.code")] + public string Code { get; set; } + + /// + /// Gets or sets a message describing the error, intended to be + /// suitable for displaying in a user interface. + /// + [JsonProperty(PropertyName = "error.message")] + public string Message { get; set; } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/RestErrorException.cs b/src/Azure/Azure.Quantum.Client/generated/Models/RestErrorException.cs new file mode 100644 index 00000000000..cb5804185a0 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/RestErrorException.cs @@ -0,0 +1,61 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Microsoft.Rest; + + /// + /// Exception thrown for an invalid response with RestError information. + /// + public partial class RestErrorException : RestException + { + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessageWrapper Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessageWrapper Response { get; set; } + + /// + /// Gets or sets the body object. + /// + public RestError Body { get; set; } + + /// + /// Initializes a new instance of the RestErrorException class. + /// + public RestErrorException() + { + } + + /// + /// Initializes a new instance of the RestErrorException class. + /// + /// The exception message. + public RestErrorException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the RestErrorException class. + /// + /// The exception message. + /// Inner exception. + public RestErrorException(string message, System.Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/Models/TargetStatus.cs b/src/Azure/Azure.Quantum.Client/generated/Models/TargetStatus.cs new file mode 100644 index 00000000000..78c150b3c95 --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/Models/TargetStatus.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client.Models +{ + using Newtonsoft.Json; + using System.Linq; + + /// + /// Target status. + /// + public partial class TargetStatus + { + /// + /// Initializes a new instance of the TargetStatus class. + /// + public TargetStatus() + { + CustomInit(); + } + + /// + /// Initializes a new instance of the TargetStatus class. + /// + /// Target id. + /// Target availability. Possible + /// values include: 'Available', 'Degraded', 'Unavailable' + /// Average queue time in + /// seconds. + /// A page with detailed status of the + /// provider. + public TargetStatus(string id = default(string), string currentAvailability = default(string), long? averageQueueTime = default(long?), string statusPage = default(string)) + { + Id = id; + CurrentAvailability = currentAvailability; + AverageQueueTime = averageQueueTime; + StatusPage = statusPage; + CustomInit(); + } + + /// + /// An initialization method that performs custom operations like setting defaults + /// + partial void CustomInit(); + + /// + /// Gets target id. + /// + [JsonProperty(PropertyName = "id")] + public string Id { get; private set; } + + /// + /// Gets target availability. Possible values include: 'Available', + /// 'Degraded', 'Unavailable' + /// + [JsonProperty(PropertyName = "currentAvailability")] + public string CurrentAvailability { get; private set; } + + /// + /// Gets average queue time in seconds. + /// + [JsonProperty(PropertyName = "averageQueueTime")] + public long? AverageQueueTime { get; private set; } + + /// + /// Gets a page with detailed status of the provider. + /// + [JsonProperty(PropertyName = "statusPage")] + public string StatusPage { get; private set; } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/ProvidersOperations.cs b/src/Azure/Azure.Quantum.Client/generated/ProvidersOperations.cs new file mode 100644 index 00000000000..625e3d0457b --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/ProvidersOperations.cs @@ -0,0 +1,397 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + /// + /// ProvidersOperations operations. + /// + internal partial class ProvidersOperations : IServiceOperations, IProvidersOperations + { + /// + /// Initializes a new instance of the ProvidersOperations class. + /// + /// + /// Reference to the service client. + /// + /// + /// Thrown when a required parameter is null + /// + internal ProvidersOperations(QuantumClient client) + { + if (client == null) + { + throw new System.ArgumentNullException("client"); + } + Client = client; + } + + /// + /// Gets a reference to the QuantumClient + /// + public QuantumClient Client { get; private set; } + + /// + /// Get provider status. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task>> GetStatusWithHttpMessagesAsync(Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (Client.SubscriptionId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.SubscriptionId"); + } + if (Client.ResourceGroupName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.ResourceGroupName"); + } + if (Client.WorkspaceName == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "this.Client.WorkspaceName"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "GetStatus", tracingParameters); + } + // Construct URL + var _baseUrl = Client.BaseUri.AbsoluteUri; + var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "v1.0/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/providerStatus").ToString(); + _url = _url.Replace("{subscriptionId}", System.Uri.EscapeDataString(Client.SubscriptionId)); + _url = _url.Replace("{resourceGroupName}", System.Uri.EscapeDataString(Client.ResourceGroupName)); + _url = _url.Replace("{workspaceName}", System.Uri.EscapeDataString(Client.WorkspaceName)); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new RestErrorException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + RestError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse>(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject>(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + /// + /// Get provider status. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// Headers that will be added to request. + /// + /// + /// The cancellation token. + /// + /// + /// Thrown when the operation returned an invalid status code + /// + /// + /// Thrown when unable to deserialize the response + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// Thrown when a required parameter is null + /// + /// + /// A response object containing the response body and response headers. + /// + public async Task>> GetStatusNextWithHttpMessagesAsync(string nextPageLink, Dictionary> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (nextPageLink == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "nextPageLink"); + } + // Tracing + bool _shouldTrace = ServiceClientTracing.IsEnabled; + string _invocationId = null; + if (_shouldTrace) + { + _invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("nextPageLink", nextPageLink); + tracingParameters.Add("cancellationToken", cancellationToken); + ServiceClientTracing.Enter(_invocationId, this, "GetStatusNext", tracingParameters); + } + // Construct URL + string _url = "{nextLink}"; + _url = _url.Replace("{nextLink}", nextPageLink); + List _queryParameters = new List(); + if (_queryParameters.Count > 0) + { + _url += (_url.Contains("?") ? "&" : "?") + string.Join("&", _queryParameters); + } + // Create HTTP transport objects + var _httpRequest = new HttpRequestMessage(); + HttpResponseMessage _httpResponse = null; + _httpRequest.Method = new HttpMethod("GET"); + _httpRequest.RequestUri = new System.Uri(_url); + // Set Headers + if (Client.GenerateClientRequestId != null && Client.GenerateClientRequestId.Value) + { + _httpRequest.Headers.TryAddWithoutValidation("x-ms-client-request-id", System.Guid.NewGuid().ToString()); + } + if (Client.AcceptLanguage != null) + { + if (_httpRequest.Headers.Contains("accept-language")) + { + _httpRequest.Headers.Remove("accept-language"); + } + _httpRequest.Headers.TryAddWithoutValidation("accept-language", Client.AcceptLanguage); + } + + + if (customHeaders != null) + { + foreach(var _header in customHeaders) + { + if (_httpRequest.Headers.Contains(_header.Key)) + { + _httpRequest.Headers.Remove(_header.Key); + } + _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value); + } + } + + // Serialize Request + string _requestContent = null; + // Set Credentials + if (Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + // Send Request + if (_shouldTrace) + { + ServiceClientTracing.SendRequest(_invocationId, _httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + if (_shouldTrace) + { + ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse); + } + HttpStatusCode _statusCode = _httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string _responseContent = null; + if ((int)_statusCode != 200) + { + var ex = new RestErrorException(string.Format("Operation returned an invalid status code '{0}'", _statusCode)); + try + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + RestError _errorBody = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject(_responseContent, Client.DeserializationSettings); + if (_errorBody != null) + { + ex.Body = _errorBody; + } + } + catch (JsonException) + { + // Ignore the exception + } + ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent); + ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent); + if (_shouldTrace) + { + ServiceClientTracing.Error(_invocationId, ex); + } + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw ex; + } + // Create Result + var _result = new AzureOperationResponse>(); + _result.Request = _httpRequest; + _result.Response = _httpResponse; + if (_httpResponse.Headers.Contains("x-ms-request-id")) + { + _result.RequestId = _httpResponse.Headers.GetValues("x-ms-request-id").FirstOrDefault(); + } + // Deserialize Response + if ((int)_statusCode == 200) + { + _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + try + { + _result.Body = Microsoft.Rest.Serialization.SafeJsonConvert.DeserializeObject>(_responseContent, Client.DeserializationSettings); + } + catch (JsonException ex) + { + _httpRequest.Dispose(); + if (_httpResponse != null) + { + _httpResponse.Dispose(); + } + throw new SerializationException("Unable to deserialize the response.", _responseContent, ex); + } + } + if (_shouldTrace) + { + ServiceClientTracing.Exit(_invocationId, _result); + } + return _result; + } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/ProvidersOperationsExtensions.cs b/src/Azure/Azure.Quantum.Client/generated/ProvidersOperationsExtensions.cs new file mode 100644 index 00000000000..649d483f64a --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/ProvidersOperationsExtensions.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Models; + using System.Threading; + using System.Threading.Tasks; + + /// + /// Extension methods for ProvidersOperations. + /// + public static partial class ProvidersOperationsExtensions + { + /// + /// Get provider status. + /// + /// + /// The operations group for this extension method. + /// + public static IPage GetStatus(this IProvidersOperations operations) + { + return operations.GetStatusAsync().GetAwaiter().GetResult(); + } + + /// + /// Get provider status. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The cancellation token. + /// + public static async Task> GetStatusAsync(this IProvidersOperations operations, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.GetStatusWithHttpMessagesAsync(null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + /// + /// Get provider status. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + public static IPage GetStatusNext(this IProvidersOperations operations, string nextPageLink) + { + return operations.GetStatusNextAsync(nextPageLink).GetAwaiter().GetResult(); + } + + /// + /// Get provider status. + /// + /// + /// The operations group for this extension method. + /// + /// + /// The NextLink from the previous successful call to List operation. + /// + /// + /// The cancellation token. + /// + public static async Task> GetStatusNextAsync(this IProvidersOperations operations, string nextPageLink, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await operations.GetStatusNextWithHttpMessagesAsync(nextPageLink, null, cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + + } +} diff --git a/src/Azure/Azure.Quantum.Client/generated/QuantumClient.cs b/src/Azure/Azure.Quantum.Client/generated/QuantumClient.cs new file mode 100644 index 00000000000..3d193cee37a --- /dev/null +++ b/src/Azure/Azure.Quantum.Client/generated/QuantumClient.cs @@ -0,0 +1,371 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator. +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace Microsoft.Azure.Quantum.Client +{ + using Microsoft.Rest; + using Microsoft.Rest.Azure; + using Microsoft.Rest.Serialization; + using Models; + using Newtonsoft.Json; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + + /// + /// Azure Quantum REST API client + /// + public partial class QuantumClient : ServiceClient, IQuantumClient, IAzureClient + { + /// + /// The base URI of the service. + /// + public System.Uri BaseUri { get; set; } + + /// + /// Gets or sets json serialization settings. + /// + public JsonSerializerSettings SerializationSettings { get; private set; } + + /// + /// Gets or sets json deserialization settings. + /// + public JsonSerializerSettings DeserializationSettings { get; private set; } + + /// + /// Credentials needed for the client to connect to Azure. + /// + public ServiceClientCredentials Credentials { get; private set; } + + /// + /// The Azure subscription ID. This is a GUID-formatted string (e.g. + /// 00000000-0000-0000-0000-000000000000) + /// + public string SubscriptionId { get; set; } + + /// + /// Name of an Azure resource group. + /// + public string ResourceGroupName { get; set; } + + /// + /// Name of the workspace. + /// + public string WorkspaceName { get; set; } + + /// + /// The preferred language for the response. + /// + public string AcceptLanguage { get; set; } + + /// + /// The retry timeout in seconds for Long Running Operations. Default value is + /// 30. + /// + public int? LongRunningOperationRetryTimeout { get; set; } + + /// + /// Whether a unique x-ms-client-request-id should be generated. When set to + /// true a unique x-ms-client-request-id value is generated and included in + /// each request. Default is true. + /// + public bool? GenerateClientRequestId { get; set; } + + /// + /// Gets the IJobsOperations. + /// + public virtual IJobsOperations Jobs { get; private set; } + + /// + /// Gets the IProvidersOperations. + /// + public virtual IProvidersOperations Providers { get; private set; } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// HttpClient to be used + /// + /// + /// True: will dispose the provided httpClient on calling QuantumClient.Dispose(). False: will not dispose provided httpClient + internal QuantumClient(HttpClient httpClient, bool disposeHttpClient) : base(httpClient, disposeHttpClient) + { + Initialize(); + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + protected QuantumClient(params DelegatingHandler[] handlers) : base(handlers) + { + Initialize(); + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + internal QuantumClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) + { + Initialize(); + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + protected QuantumClient(System.Uri baseUri, params DelegatingHandler[] handlers) : this(handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + BaseUri = baseUri; + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + protected QuantumClient(System.Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + BaseUri = baseUri; + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Required. Credentials needed for the client to connect to Azure. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + public QuantumClient(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) + { + if (credentials == null) + { + throw new System.ArgumentNullException("credentials"); + } + Credentials = credentials; + if (Credentials != null) + { + Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Required. Credentials needed for the client to connect to Azure. + /// + /// + /// HttpClient to be used + /// + /// + /// True: will dispose the provided httpClient on calling QuantumClient.Dispose(). False: will not dispose provided httpClient + /// + /// Thrown when a required parameter is null + /// + public QuantumClient(ServiceClientCredentials credentials, HttpClient httpClient, bool disposeHttpClient) : this(httpClient, disposeHttpClient) + { + if (credentials == null) + { + throw new System.ArgumentNullException("credentials"); + } + Credentials = credentials; + if (Credentials != null) + { + Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Required. Credentials needed for the client to connect to Azure. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + public QuantumClient(ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) + { + if (credentials == null) + { + throw new System.ArgumentNullException("credentials"); + } + Credentials = credentials; + if (Credentials != null) + { + Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Required. Credentials needed for the client to connect to Azure. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + public QuantumClient(System.Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) : this(handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + if (credentials == null) + { + throw new System.ArgumentNullException("credentials"); + } + BaseUri = baseUri; + Credentials = credentials; + if (Credentials != null) + { + Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes a new instance of the QuantumClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Required. Credentials needed for the client to connect to Azure. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The delegating handlers to add to the http client pipeline. + /// + /// + /// Thrown when a required parameter is null + /// + public QuantumClient(System.Uri baseUri, ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : this(rootHandler, handlers) + { + if (baseUri == null) + { + throw new System.ArgumentNullException("baseUri"); + } + if (credentials == null) + { + throw new System.ArgumentNullException("credentials"); + } + BaseUri = baseUri; + Credentials = credentials; + if (Credentials != null) + { + Credentials.InitializeServiceClient(this); + } + } + + /// + /// An optional partial-method to perform custom initialization. + /// + partial void CustomInitialize(); + /// + /// Initializes client properties. + /// + private void Initialize() + { + Jobs = new JobsOperations(this); + Providers = new ProvidersOperations(this); + BaseUri = new System.Uri("https://app-jobscheduler-prod.azurewebsites.net"); + AcceptLanguage = "en-US"; + LongRunningOperationRetryTimeout = 30; + GenerateClientRequestId = true; + SerializationSettings = new JsonSerializerSettings + { + Formatting = Newtonsoft.Json.Formatting.Indented, + DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + SerializationSettings.Converters.Add(new TransformationJsonConverter()); + DeserializationSettings = new JsonSerializerSettings + { + DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc, + NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore, + ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + CustomInitialize(); + DeserializationSettings.Converters.Add(new TransformationJsonConverter()); + DeserializationSettings.Converters.Add(new CloudErrorJsonConverter()); + } + } +} diff --git a/src/Azure/Common/AssemblyCommon.props b/src/Azure/Common/AssemblyCommon.props new file mode 100644 index 00000000000..79fb4f744d9 --- /dev/null +++ b/src/Azure/Common/AssemblyCommon.props @@ -0,0 +1,23 @@ + + + + Microsoft + Microsoft + Microsoft Quantum Development Kit Preview + © Microsoft Corporation. All rights reserved. + + + + + + + + + \ No newline at end of file diff --git a/src/Azure/Common/DelaySign.cs b/src/Azure/Common/DelaySign.cs new file mode 100644 index 00000000000..03a252785d8 --- /dev/null +++ b/src/Azure/Common/DelaySign.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Reflection; + +// Attributes for delay-signing +#if SIGNED +[assembly:AssemblyKeyFile("..\\..\\..\\Build\\267DevDivSNKey2048.snk")] +[assembly:AssemblyDelaySign(true)] +#endif + +internal static class DelaySign +{ +#pragma warning disable SA1310 // Fields should not have underscore. +#if SIGNED + public const string PUBLIC_KEY = ", PublicKey=" + + "002400000c800000140100000602000000240000525341310008000001000100613399aff18ef1" + + "a2c2514a273a42d9042b72321f1757102df9ebada69923e2738406c21e5b801552ab8d200a65a2" + + "35e001ac9adc25f2d811eb09496a4c6a59d4619589c69f5baf0c4179a47311d92555cd006acc8b" + + "5959f2bd6e10e360c34537a1d266da8085856583c85d81da7f3ec01ed9564c58d93d713cd0172c" + + "8e23a10f0239b80c96b07736f5d8b022542a4e74251a5f432824318b3539a5a087f8e53d2f135f" + + "9ca47f3bb2e10aff0af0849504fb7cea3ff192dc8de0edad64c68efde34c56d302ad55fd6e80f3" + + "02d5efcdeae953658d3452561b5f36c542efdbdd9f888538d374cef106acf7d93a4445c3c73cd9" + + "11f0571aaf3d54da12b11ddec375b3"; +#else + public const string PUBLIC_KEY = ""; +#endif +} diff --git a/src/Azure/Common/StyleCop.ruleset b/src/Azure/Common/StyleCop.ruleset new file mode 100644 index 00000000000..24532383c6b --- /dev/null +++ b/src/Azure/Common/StyleCop.ruleset @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Azure/Common/stylecop.json b/src/Azure/Common/stylecop.json new file mode 100644 index 00000000000..79ab17994dd --- /dev/null +++ b/src/Azure/Common/stylecop.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Microsoft Corporation" + }, + "orderingRules": { + "usingDirectivesPlacement": "outsideNamespace" + } + } +} diff --git a/src/Simulation/Common/Exceptions/QuantumProcessorTranslationException.cs b/src/Simulation/Common/Exceptions/QuantumProcessorTranslationException.cs index 5ef32c382dd..3ba8df02673 100644 --- a/src/Simulation/Common/Exceptions/QuantumProcessorTranslationException.cs +++ b/src/Simulation/Common/Exceptions/QuantumProcessorTranslationException.cs @@ -7,9 +7,19 @@ namespace Microsoft.Quantum.Simulation.Common.Exceptions { public class QuantumProcessorTranslationException : Exception { - public QuantumProcessorTranslationException(string message = "An exception occurred while performing a translation on a quantum processor.") + public QuantumProcessorTranslationException() + : base("An exception occurred while performing a translation on a quantum processor.") + { + } + + public QuantumProcessorTranslationException(string message) : base(message) { } + + public QuantumProcessorTranslationException(string message, Exception inner) + : base(message, inner) + { + } } } diff --git a/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj index 617f1637dbb..45d4e5dfe37 100644 --- a/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration.Tests/Tests.CsharpGeneration.fsproj @@ -31,12 +31,8 @@ PreserveNewest - - PreserveNewest - - @@ -46,7 +42,6 @@ - all diff --git a/src/Simulation/CsharpGeneration/EntryPoint.fs b/src/Simulation/CsharpGeneration/EntryPoint.fs index 7a2148ea449..b975bf374a8 100644 --- a/src/Simulation/CsharpGeneration/EntryPoint.fs +++ b/src/Simulation/CsharpGeneration/EntryPoint.fs @@ -11,8 +11,6 @@ open Microsoft.Quantum.QsCompiler.SyntaxTokens open Microsoft.Quantum.QsCompiler.SyntaxTree open Microsoft.Quantum.RoslynWrapper open System -open System.IO -open System.Reflection /// An entry point parameter. @@ -22,38 +20,11 @@ type private Parameter = CsharpTypeName : string Description : string } -/// The namespace in which to put generated code for the entry point. -let generatedNamespace entryPointNamespace = entryPointNamespace + ".__QsEntryPoint__" +/// The name of the generated entry point class. +let entryPointClassName = "__QsEntryPoint__" -/// A public constant field. -let private constant name typeName value = - ``field`` typeName name [``public``; ``const``] (``:=`` value |> Some) - -/// A public static property with a getter. -let private readonlyProperty name typeName value = - ``property-arrow_get`` typeName name [``public``; ``static``] - ``get`` (``=>`` value) - -/// A static class containing constants used by the entry point driver. -let private constantsClass = - ``class`` "Constants" ``<<`` [] ``>>`` - ``:`` None ``,`` [] - [``internal``; ``static``] - ``{`` - [readonlyProperty "SimulatorOptions" "System.Collections.Generic.IEnumerable" - (``new array`` (Some "") [``literal`` ("--" + fst CommandLineArguments.SimulatorOption) - ``literal`` ("-" + snd CommandLineArguments.SimulatorOption)]) - constant "QuantumSimulator" "string" (``literal`` AssemblyConstants.QuantumSimulator) - constant "ToffoliSimulator" "string" (``literal`` AssemblyConstants.ToffoliSimulator) - constant "ResourcesEstimator" "string" (``literal`` AssemblyConstants.ResourcesEstimator)] - ``}`` - -/// The name of the C# type used by the parameter in its command-line option, given its Q# type. -let rec private csharpParameterTypeName context (qsType : ResolvedType) = - match qsType.Resolution with - | ArrayType itemType -> sprintf "System.Collections.Generic.IEnumerable<%s>" - (csharpParameterTypeName context itemType) - | _ -> SimulationCode.roslynTypeName context qsType +/// The namespace containing the non-generated parts of the entry point driver. +let private driverNamespace = "Microsoft.Quantum.CsharpGeneration.EntryPointDriver" /// A sequence of all of the named parameters in the argument tuple and their respective C# and Q# types. let rec private parameters context doc = function @@ -62,166 +33,124 @@ let rec private parameters context doc = function | ValidName name -> Seq.singleton { Name = name.Value QsharpType = variable.Type - CsharpTypeName = csharpParameterTypeName context variable.Type + CsharpTypeName = SimulationCode.roslynTypeName context variable.Type Description = ParameterDescription doc name.Value } | InvalidName -> Seq.empty | QsTuple items -> items |> Seq.map (parameters context doc) |> Seq.concat -/// The argument parser for the Q# type. -let private argumentParser qsType = - let rec valueParser = function - | ArrayType (itemType : ResolvedType) -> valueParser itemType.Resolution - | BigInt -> Some "TryParseBigInteger" - | Range -> Some "TryParseQRange" - | Result -> Some "TryParseResult" - | UnitType -> Some "TryParseQVoid" - | _ -> None - let argParser = - match qsType with - | ArrayType _ -> "ParseArgumentsWith" - | _ -> "ParseArgumentWith" - valueParser qsType |> Option.map (fun valueParser -> - ``ident`` "Parsers" <.> (``ident`` argParser, [``ident`` "Parsers" <|.|> ``ident`` valueParser])) - -/// Adds suggestions, if any, to the option based on the Q# type. -let private withSuggestions qsType option = - let rec suggestions = function - | ArrayType (itemType : ResolvedType) -> suggestions itemType.Resolution - | Result -> ["Zero"; "One"] - | _ -> [] - match suggestions qsType with - | [] -> option - | suggestions -> - let args = option :: List.map ``literal`` suggestions - ``invoke`` (``ident`` "System.CommandLine.OptionExtensions.WithSuggestions") ``(`` args ``)`` +/// An expression representing the name of an entry point option given its parameter name. +let private optionName (paramName : string) = + let toKebabCaseIdent = ident "System.CommandLine.Parsing.StringExtensions.ToKebabCase" + if paramName.Length = 1 + then literal ("-" + paramName) + else literal "--" <+> invoke toKebabCaseIdent ``(`` [literal paramName] ``)`` /// A property containing a sequence of command-line options corresponding to each parameter given. let private parameterOptionsProperty parameters = let optionTypeName = "System.CommandLine.Option" let optionsEnumerableTypeName = sprintf "System.Collections.Generic.IEnumerable<%s>" optionTypeName - let toKebabCaseIdent = ``ident`` "System.CommandLine.Parsing.StringExtensions.ToKebabCase" - let getOption { Name = name; QsharpType = qsType; CsharpTypeName = typeName; Description = desc } = - let nameExpr = - if name.Length = 1 - then ``literal`` ("-" + name) - else ``literal`` "--" <+> ``invoke`` toKebabCaseIdent ``(`` [``literal`` name] ``)`` - let args = - match argumentParser qsType.Resolution with - | Some parser -> [nameExpr; parser; upcast ``false``; ``literal`` desc] - | None -> [nameExpr; ``literal`` desc] - - ``new init`` (``type`` [sprintf "%s<%s>" optionTypeName typeName]) ``(`` args ``)`` - ``{`` - [``ident`` "Required" <-- ``true``] - ``}`` - |> withSuggestions qsType.Resolution - - let options = parameters |> Seq.map getOption |> Seq.toList - ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``; ``static``] - ``get`` (``=>`` (``new array`` (Some optionTypeName) options)) - -/// The name of the parameter property for the given parameter name. -let private parameterPropertyName (s : string) = s.Substring(0, 1).ToUpper() + s.Substring 1 - -/// A sequence of properties corresponding to each parameter given. -let private parameterProperties = - Seq.map (fun { Name = name; CsharpTypeName = typeName } -> - ``prop`` typeName (parameterPropertyName name) [``public``]) - -/// The method for running the entry point using the parameter properties declared in the adapter. -let private runMethod context (entryPoint : QsCallable) = - let entryPointName = sprintf "%s.%s" entryPoint.FullName.Namespace.Value entryPoint.FullName.Name.Value - let returnTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ReturnType - let taskTypeName = sprintf "System.Threading.Tasks.Task<%s>" returnTypeName - let factoryParamName = "__factory__" - - let argExpr { Name = name; QsharpType = qsType } = - let property = ``ident`` "this" <|.|> ``ident`` (parameterPropertyName name) - match qsType.Resolution with - | ArrayType itemType -> - let arrayTypeName = sprintf "QArray<%s>" (SimulationCode.roslynTypeName context itemType) - ``new`` (``type`` arrayTypeName) ``(`` [property] ``)`` - | _ -> property - - let callArgs : seq = - Seq.concat [ - Seq.singleton (upcast ``ident`` factoryParamName) - Seq.map argExpr (parameters context entryPoint.Documentation entryPoint.ArgumentTuple) - ] - - ``arrow_method`` taskTypeName "Run" ``<<`` [] ``>>`` - ``(`` [``param`` factoryParamName ``of`` (``type`` "IOperationFactory")] ``)`` - [``public``; ``async``] - (Some (``=>`` (``await`` (``ident`` entryPointName <.> (``ident`` "Run", callArgs))))) + let option { Name = name; CsharpTypeName = typeName; Description = desc } = + let createOption = ident (sprintf "%s.Options.CreateOption<%s>" driverNamespace typeName) + let args = [optionName name; literal desc] + invoke createOption ``(`` args ``)`` + let options = parameters |> Seq.map option |> Seq.toList + ``property-arrow_get`` optionsEnumerableTypeName "Options" [``public``] + get (``=>`` (``new array`` (Some optionTypeName) options)) /// A method that creates an instance of the default simulator if it is a custom simulator. let private customSimulatorFactory name = - let expr : ExpressionSyntax = - if name = AssemblyConstants.QuantumSimulator || - name = AssemblyConstants.ToffoliSimulator || - name = AssemblyConstants.ResourcesEstimator - then upcast SyntaxFactory.ThrowExpression (``new`` (``type`` "InvalidOperationException") ``(`` [] ``)``) - else ``new`` (``type`` name) ``(`` [] ``)`` - ``arrow_method`` "IOperationFactory" "CreateDefaultCustomSimulator" ``<<`` [] ``>>`` + let isCustomSimulator = + not <| List.contains name [ + AssemblyConstants.QuantumSimulator + AssemblyConstants.ToffoliSimulator + AssemblyConstants.ResourcesEstimator + ] + let factory = + if isCustomSimulator + then ``new`` (``type`` name) ``(`` [] ``)`` + else upcast SyntaxFactory.ThrowExpression (``new`` (``type`` "InvalidOperationException") ``(`` [] ``)``) + + arrow_method "IOperationFactory" "CreateDefaultCustomSimulator" ``<<`` [] ``>>`` ``(`` [] ``)`` - [``public``; ``static``] - (Some (``=>`` expr)) + [``public``] + (Some (``=>`` factory)) + +/// A method that creates the argument tuple for the entry point, given the command-line parsing result. +let private createArgument context entryPoint = + let inTypeName = SimulationCode.roslynTypeName context entryPoint.Signature.ArgumentType + let parseResultName = "parseResult" + let valueForArg (name, typeName) = + ident parseResultName <.> (sprintf "ValueForOption<%s>" typeName |> ident, [optionName name]) + let argTuple = + SimulationCode.mapArgumentTuple + valueForArg + context + entryPoint.ArgumentTuple + entryPoint.Signature.ArgumentType + arrow_method inTypeName "CreateArgument" ``<<`` [] ``>>`` + ``(`` [param parseResultName ``of`` (``type`` "System.CommandLine.Parsing.ParseResult")] ``)`` + [``public``] + (Some (``=>`` argTuple)) + +/// A tuple of the callable's name, argument type name, and return type name. +let private callableTypeNames context (callable : QsCallable) = + let callableName = sprintf "%s.%s" callable.FullName.Namespace.Value callable.FullName.Name.Value + let argTypeName = SimulationCode.roslynTypeName context callable.Signature.ArgumentType + let returnTypeName = SimulationCode.roslynTypeName context callable.Signature.ReturnType + callableName, argTypeName, returnTypeName + +/// The main method for the standalone executable. +let private mainMethod context entryPoint = + let callableName, argTypeName, returnTypeName = callableTypeNames context entryPoint + let driverType = generic (driverNamespace + ".Driver") ``<<`` [callableName; argTypeName; returnTypeName] ``>>`` + let entryPointInstance = ``new`` (``type`` entryPointClassName) ``(`` [] ``)`` + let driver = ``new`` driverType ``(`` [entryPointInstance] ``)`` + let commandLineArgsName = "args" + arrow_method "System.Threading.Tasks.Task" "Main" ``<<`` [] ``>>`` + ``(`` [param commandLineArgsName ``of`` (``type`` "string[]")] ``)`` + [``private``; ``static``; async] + (Some (``=>`` (await (driver <.> (ident "Run", [ident commandLineArgsName]))))) /// The class that adapts the entry point for use with the command-line parsing library and the driver. -let private adapterClass context (entryPoint : QsCallable) = - let summaryProperty = - readonlyProperty "Summary" "string" (``literal`` ((PrintSummary entryPoint.Documentation false).Trim ())) +let private entryPointClass context entryPoint = + let callableName, argTypeName, returnTypeName = callableTypeNames context entryPoint + let property name typeName value = ``property-arrow_get`` typeName name [``public``] get (``=>`` value) + let summaryProperty = property "Summary" "string" (literal ((PrintSummary entryPoint.Documentation false).Trim ())) + let parameters = parameters context entryPoint.Documentation entryPoint.ArgumentTuple let defaultSimulator = context.assemblyConstants.TryGetValue AssemblyConstants.DefaultSimulator |> snd |> (fun value -> if String.IsNullOrWhiteSpace value then AssemblyConstants.QuantumSimulator else value) - let defaultSimulatorProperty = readonlyProperty "DefaultSimulator" "string" (``literal`` defaultSimulator) - let parameters = parameters context entryPoint.Documentation entryPoint.ArgumentTuple - - let members : seq = - Seq.concat [ - Seq.ofList [ - summaryProperty - defaultSimulatorProperty - parameterOptionsProperty parameters - customSimulatorFactory defaultSimulator - runMethod context entryPoint - ] - parameterProperties parameters |> Seq.map (fun property -> upcast property) - ] - - ``class`` "EntryPoint" ``<<`` [] ``>>`` - ``:`` None ``,`` [] + let defaultSimulatorProperty = property "DefaultSimulator" "string" (literal defaultSimulator) + let infoProperty = + property "Info" + (sprintf "EntryPointInfo<%s, %s>" argTypeName returnTypeName) + (ident callableName <|.|> ident "Info") + let members : MemberDeclarationSyntax list = [ + summaryProperty + parameterOptionsProperty parameters + defaultSimulatorProperty + infoProperty + customSimulatorFactory defaultSimulator + createArgument context entryPoint + mainMethod context entryPoint + ] + let baseName = sprintf "%s.IEntryPoint<%s, %s>" driverNamespace argTypeName returnTypeName + ``class`` entryPointClassName``<<`` [] ``>>`` + ``:`` (Some (simpleBase baseName)) ``,`` [] [``internal``] ``{`` members ``}`` -/// The source code for the entry point constants and adapter classes. -let private generatedClasses context (entryPoint : QsCallable) = +/// Generates C# source code for a standalone executable that runs the Q# entry point. +let generate context (entryPoint : QsCallable) = let ns = - ``namespace`` (generatedNamespace entryPoint.FullName.Namespace.Value) + ``namespace`` entryPoint.FullName.Namespace.Value ``{`` - (Seq.map ``using`` SimulationCode.autoNamespaces) - [ - constantsClass - adapterClass context entryPoint - ] + (Seq.map using SimulationCode.autoNamespaces) + [entryPointClass context entryPoint] ``}`` ``compilation unit`` [] [] [ns] |> ``with leading comments`` SimulationCode.autogenComment |> SimulationCode.formatSyntaxTree - |> fun code -> code + Environment.NewLine - -/// The source code for the entry point driver. -let private driver (entryPoint : QsCallable) = - let source fileName = - let resourceName = "Microsoft.Quantum.CsharpGeneration.Resources.EntryPoint." + fileName - use stream = Assembly.GetExecutingAssembly().GetManifestResourceStream resourceName - use reader = new StreamReader(stream) - reader.ReadToEnd().Replace("@Namespace", generatedNamespace entryPoint.FullName.Namespace.Value) - String.Join (Environment.NewLine, source "Driver.cs", source "Parsers.cs", source "Validation.cs") - -/// Generates C# source code for a standalone executable that runs the Q# entry point. -let generate context entryPoint = - generatedClasses context entryPoint + driver entryPoint diff --git a/src/Simulation/CsharpGeneration/FindNuspecReferences.ps1 b/src/Simulation/CsharpGeneration/FindNuspecReferences.ps1 index 94efa325ab8..68d012099af 100644 --- a/src/Simulation/CsharpGeneration/FindNuspecReferences.ps1 +++ b/src/Simulation/CsharpGeneration/FindNuspecReferences.ps1 @@ -19,53 +19,53 @@ # nuget is tracking this problem at: https://github.com/NuGet/Home/issues/4491 ######################################## -$target = "Microsoft.Quantum.CsharpGeneration.nuspec" +using namespace System.IO -if (Test-Path $target) { +$target = 'Microsoft.Quantum.CsharpGeneration.nuspec' +if (Test-Path $target) { Write-Host "$target exists. Skipping generating new one." exit - } - -# Start with the nuspec template -$nuspec = [xml](Get-Content "Microsoft.Quantum.CsharpGeneration.nuspec.template") -$dep = $nuspec.CreateElement('dependencies', $nuspec.package.metadata.NamespaceURI) +} +$nuspec = [Xml](Get-Content 'Microsoft.Quantum.CsharpGeneration.nuspec.template') +$dependencies = $nuspec.CreateElement('dependencies', $nuspec.package.metadata.NamespaceURI) -# Recursively find PackageReferences on all ProjectReferences: -function Add-NuGetDependencyFromCsprojToNuspec($PathToCsproj) -{ - $csproj = [xml](Get-Content $PathToCsproj) +# Adds a dependency to the dependencies element if it does not already exist. +function Add-Dependency($Id, $Version) { + if (-not ($dependencies.dependency | Where-Object { $_.id -eq $Id })) { + Write-Host "Adding dependency $Id." + $dependency = $nuspec.CreateElement('dependency', $nuspec.package.metadata.NamespaceURI) + $dependency.SetAttribute('id', $Id) + $dependency.SetAttribute('version', $Version) + $dependencies.AppendChild($dependency) + } +} - # Find all PackageReferences nodes: - $packageDependency = $csproj.Project.ItemGroup.PackageReference | Where-Object { $null -ne $_ } - $packageDependency | ForEach-Object { - # Identify package's id either from "Include" or "Update" attribute: - $id = $_.Include - if ($id -eq $null -or $id -eq "") { - $id = $_.Update - } +# Recursively find PackageReferences on all ProjectReferences. +function Add-PackageReferenceDependencies($ProjectFileName) { + $project = [Xml](Get-Content $ProjectFileName) - # Check if package already added as dependency, only add if new: - $added = $dep.dependency | Where { $_.id -eq $id } - if (!$added) { - Write-Host "Adding $id" - $onedependency = $dep.AppendChild($nuspec.CreateElement('dependency', $nuspec.package.metadata.NamespaceURI)) - $onedependency.SetAttribute('id', $id) - $onedependency.SetAttribute('version', $_.Version) - } + # Add all package references as dependencies. + $project.Project.ItemGroup.PackageReference | Where-Object { $null -ne $_ } | ForEach-Object { + $id = if ($_.Include) { $_.Include } else { $_.Update } + Add-Dependency $id $_.Version } - # Recursively check on project references: - $projectDependency = $csproj.Project.ItemGroup.ProjectReference | Where-Object { $null -ne $_ } - $projectDependency | ForEach-Object { - Add-NuGetDependencyFromCsprojToNuspec $_.Include $dep + # Recursively add dependencies from all project references. + $project.Project.ItemGroup.ProjectReference | Where-Object { $null -ne $_ } | ForEach-Object { + Add-PackageReferenceDependencies $_.Include } } -# Find all dependencies on Microsoft.Quantum.CsharpGeneration.fsproj -Add-NuGetDependencyFromCsprojToNuspec "Microsoft.Quantum.CsharpGeneration.fsproj" $dep +# Add dependencies for the projects included in this NuGet package. +Add-PackageReferenceDependencies 'Microsoft.Quantum.CsharpGeneration.fsproj' +Add-PackageReferenceDependencies '..\EntryPointDriver\EntryPointDriver.csproj' -# Save into .nuspec file: -$nuspec.package.metadata.AppendChild($dep) -$nuspec.Save($target) +# Manually add EntryPointDriver's project references as package references to avoid a build-time dependency cycle. +# $version$ is replaced with the current package version when the package is built. +Add-Dependency 'Microsoft.Quantum.Runtime.Core' '$version$' +Add-Dependency 'Microsoft.Quantum.Simulators' '$version$' +Add-Dependency 'Microsoft.Azure.Quantum.Client' '$version$' +$nuspec.package.metadata.AppendChild($dependencies) +$nuspec.Save([Path]::Combine((Get-Location), $target)) diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj index abd7790b284..68f32ff4c67 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.fsproj @@ -8,9 +8,6 @@ - - - @@ -30,5 +27,4 @@ - diff --git a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.nuspec.template b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.nuspec.template index c22cf3d6572..cf3797a7b0a 100644 --- a/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.nuspec.template +++ b/src/Simulation/CsharpGeneration/Microsoft.Quantum.CsharpGeneration.nuspec.template @@ -1,5 +1,5 @@ - + Microsoft.Quantum.CsharpGeneration $version$ @@ -17,11 +17,18 @@ See: https://docs.microsoft.com/en-us/quantum/relnotes/ $copyright$ - Quantum Q# Qsharp + Quantum Q# Qsharp + + + + diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs deleted file mode 100644 index d461e0f133e..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Driver.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace @Namespace -{ - using Microsoft.Quantum.Simulation.Core; - using Microsoft.Quantum.Simulation.Simulators; - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.CommandLine; - using System.CommandLine.Builder; - using System.CommandLine.Help; - using System.CommandLine.Invocation; - using System.CommandLine.Parsing; - using System.Linq; - using System.Threading.Tasks; - - /// - /// The entry point driver is the entry point for the C# application that executes the Q# entry point. - /// - internal static class Driver - { - /// - /// A modification of the command line class. - /// - private sealed class QsHelpBuilder : HelpBuilder - { - public QsHelpBuilder(IConsole console) : base(console) { } - - protected override string ArgumentDescriptor(IArgument argument) - { - // Hide long argument descriptors. - var descriptor = base.ArgumentDescriptor(argument); - return descriptor.Length > 30 ? argument.Name : descriptor; - } - } - - /// - /// Runs the entry point. - /// - /// The command-line arguments. - /// The exit code. - private static async Task Main(string[] args) - { - var simulate = new Command("simulate", "(default) Run the program using a local simulator."); - TryCreateOption( - Constants.SimulatorOptions, - () => EntryPoint.DefaultSimulator, - "The name of the simulator to use.").Then(option => - { - option.Argument.AddSuggestions(ImmutableHashSet.Empty - .Add(Constants.QuantumSimulator) - .Add(Constants.ToffoliSimulator) - .Add(Constants.ResourcesEstimator) - .Add(EntryPoint.DefaultSimulator)); - simulate.AddOption(option); - }); - simulate.Handler = CommandHandler.Create(Simulate); - - var root = new RootCommand(EntryPoint.Summary) { simulate }; - foreach (var option in EntryPoint.Options) { root.AddGlobalOption(option); } - - // Set the simulate command as the default. - foreach (var option in simulate.Options) { root.AddOption(option); } - root.Handler = simulate.Handler; - - return await new CommandLineBuilder(root) - .UseDefaults() - .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) - .Build() - .InvokeAsync(args); - } - - /// - /// Simulates the entry point. - /// - /// The entry point. - /// The simulator to use. - /// The exit code. - private static async Task Simulate(EntryPoint entryPoint, string simulator) - { - simulator = DefaultIfShadowed(Constants.SimulatorOptions.First(), simulator, EntryPoint.DefaultSimulator); - switch (simulator) - { - case Constants.ResourcesEstimator: - var resourcesEstimator = new ResourcesEstimator(); - await entryPoint.Run(resourcesEstimator); - Console.WriteLine(resourcesEstimator.ToTSV()); - break; - default: - var (isCustom, createSimulator) = simulator switch - { - Constants.QuantumSimulator => - (false, new Func(() => new QuantumSimulator())), - Constants.ToffoliSimulator => - (false, new Func(() => new ToffoliSimulator())), - _ => (true, EntryPoint.CreateDefaultCustomSimulator) - }; - if (isCustom && simulator != EntryPoint.DefaultSimulator) - { - DisplayCustomSimulatorError(simulator); - return 1; - } - await DisplayEntryPointResult(entryPoint, createSimulator); - break; - } - return 0; - } - - /// - /// Runs the entry point on a simulator and displays its return value. - /// - /// The entry point. - /// A function that creates an instance of the simulator to use. - private static async Task DisplayEntryPointResult( - EntryPoint entryPoint, Func createSimulator) - { - var simulator = createSimulator(); - try - { - var value = await entryPoint.Run(simulator); -#pragma warning disable CS0184 - if (!(value is QVoid)) -#pragma warning restore CS0184 - { - Console.WriteLine(value); - } - } - finally - { - if (simulator is IDisposable disposable) - { - disposable.Dispose(); - } - } - } - - /// - /// Returns true if the alias is available for use by the driver (that is, the alias is not already used by an - /// entry point option). - /// - /// The alias to check. - /// True if the alias is available for use by the driver. - private static bool IsAliasAvailable(string alias) => - !EntryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); - - /// - /// Returns the default value and displays a warning if the alias is shadowed by an entry point option, and - /// returns the original value otherwise. - /// - /// The type of the values. - /// The primary option alias corresponding to the value. - /// The value of the option given on the command line. - /// The default value for the option. - /// - private static T DefaultIfShadowed(string alias, T value, T defaultValue) - { - if (IsAliasAvailable(alias)) - { - return value; - } - else - { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.Error.WriteLine($"Warning: Option {alias} is overriden by an entry point parameter name."); - Console.Error.WriteLine($" Using default value {defaultValue}."); - Console.ForegroundColor = originalForeground; - return defaultValue; - } - } - - /// - /// Tries to create an option by removing aliases that are already in use by the entry point. If the first - /// alias, which is considered the primary alias, is in use, then the option is not created. - /// - /// The type of the option's argument. - /// The option's aliases. - /// A function that returns the option's default value. - /// The option's description. - /// A validation of the option. - private static Validation> TryCreateOption( - IEnumerable aliases, Func getDefaultValue, string description = null) => - IsAliasAvailable(aliases.First()) - ? Validation>.Success( - new Option(aliases.Where(IsAliasAvailable).ToArray(), getDefaultValue, description)) - : Validation>.Failure(); - - /// - /// Displays an error message for using a non-default custom simulator. - /// - /// The name of the custom simulator. - private static void DisplayCustomSimulatorError(string name) - { - var originalForeground = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine($"The simulator '{name}' could not be found."); - Console.ForegroundColor = originalForeground; - Console.Error.WriteLine(); - Console.Error.WriteLine( - $"If '{name}' is a custom simulator, it must be set in the DefaultSimulator project property:"); - Console.Error.WriteLine(); - Console.Error.WriteLine(""); - Console.Error.WriteLine($" {name}"); - Console.Error.WriteLine(""); - } - } -} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs deleted file mode 100644 index fa34503db66..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Parsers.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace @Namespace -{ - using Microsoft.Quantum.Simulation.Core; - using System; - using System.Collections.Generic; - using System.CommandLine.Parsing; - using System.Linq; - using System.Numerics; - - /// - /// A delegate that parses the value and returns a validation. - /// - /// The type parsed value. - /// The string to parse. - /// The name of the option that the value was used with. - /// A validation of the parsed value. - internal delegate Validation TryParseValue(string value, string optionName = null); - - /// - /// Parsers for command-line arguments. - /// - internal static class Parsers - { - /// - /// Creates an argument parser for a many-valued argument using a parser that operates on each string value. - /// - /// The type of the parsed value. - /// The string parser. - /// The argument parser. - internal static ParseArgument> ParseArgumentsWith(TryParseValue parse) => argument => - { - var optionName = ((OptionResult)argument.Parent).Token.Value; - var validation = argument.Tokens.Select(token => parse(token.Value, optionName)).Sequence(); - if (validation.IsFailure) - { - argument.ErrorMessage = validation.ErrorMessage; - } - return validation.ValueOrDefault; - }; - - /// - /// Creates an argument parser for a single-valued argument using a parser that operates on the string value. - /// - /// The type of the parsed value. - /// The string parser. - /// The argument parser. - internal static ParseArgument ParseArgumentWith(TryParseValue parse) => argument => - { - var values = ParseArgumentsWith(parse)(argument); - return values == null ? default : values.Single(); - }; - - /// - /// Parses a . - /// - /// The string value to parse. - /// The name of the option that the value was used with. - /// A validation of the parsed . - internal static Validation TryParseBigInteger(string value, string optionName = null) => - BigInteger.TryParse(value, out var result) - ? Validation.Success(result) - : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(BigInteger))); - - /// - /// Parses a . - /// - /// The string value to parse. - /// The name of the option that the value was used with. - /// A validation of the parsed . - internal static Validation TryParseQRange(string value, string optionName = null) - { - Validation tryParseLong(string longValue) => - long.TryParse(longValue, out var result) - ? Validation.Success(result) - : Validation.Failure(GetArgumentErrorMessage(longValue, optionName, typeof(long))); - - return value.Split("..").Select(tryParseLong).Sequence().Bind(values => - values.Count() == 2 - ? Validation.Success(new QRange(values.ElementAt(0), values.ElementAt(1))) - : values.Count() == 3 - ? Validation.Success(new QRange(values.ElementAt(0), values.ElementAt(1), values.ElementAt(2))) - : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(QRange)))); - } - - /// - /// Parses a . - /// - /// The string value to parse. - /// The name of the option that the value was used with. - /// A validation of the parsed . - internal static Validation TryParseQVoid(string value, string optionName = null) => - value.Trim() == QVoid.Instance.ToString() - ? Validation.Success(QVoid.Instance) - : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(QVoid))); - - /// - /// Parses a . - /// - /// The string value to parse. - /// The name of the option that the value was used with. - /// A validation of the parsed . - internal static Validation TryParseResult(string value, string optionName = null) => - Enum.TryParse(value, ignoreCase: true, out ResultValue result) - ? Validation.Success(result switch - { - ResultValue.Zero => Result.Zero, - ResultValue.One => Result.One, - var invalid => throw new Exception($"Invalid result value '{invalid}'.") - }) - : Validation.Failure(GetArgumentErrorMessage(value, optionName, typeof(Result))); - - /// - /// Returns an error message string for an argument parser. - /// - /// The value of the argument being parsed. - /// The name of the option. - /// The expected type of the argument. - /// An error message string for an argument parser. - private static string GetArgumentErrorMessage(string arg, string optionName, Type type) => - $"Cannot parse argument '{arg}' for option '{optionName}' as expected type {type}."; - } -} diff --git a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs b/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs deleted file mode 100644 index 20b8eaf6571..00000000000 --- a/src/Simulation/CsharpGeneration/Resources/EntryPoint/Validation.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace @Namespace -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Represents either a success or a failure of a process. - /// - /// The type of the success value. - internal struct Validation - { - public bool IsSuccess { get; } - public bool IsFailure { get => !IsSuccess; } - public T Value { get => IsSuccess ? ValueOrDefault : throw new InvalidOperationException(); } - public T ValueOrDefault { get; } - public string ErrorMessage { get; } - - private Validation(bool isSuccess, T value, string errorMessage) - { - IsSuccess = isSuccess; - ValueOrDefault = value; - ErrorMessage = errorMessage; - } - - public static Validation Success(T value) => - new Validation(true, value, default); - - public static Validation Failure(string errorMessage = null) => - new Validation(false, default, errorMessage); - } - - /// - /// Extension methods for . - /// - internal static class ValidationExtensions - { - /// - /// Sequentially composes two validations, passing the value of the first validation to another - /// validation-producing function if the first validation is a success. - /// - /// The type of the first validation's success value. - /// The type of the second validation's success value. - /// The first validation. - /// - /// A function that takes the value of the first validation and returns a second validation. - /// - /// - /// The first validation if the first validation is a failure; otherwise, the return value of calling the bind - /// function on the first validation's success value. - /// - internal static Validation Bind(this Validation validation, Func> bind) => - validation.IsFailure ? Validation.Failure(validation.ErrorMessage) : bind(validation.Value); - - /// - /// Converts an enumerable of validations into a validation of an enumerable. - /// - /// The type of the validation success values. - /// The validations to sequence. - /// - /// A validation that contains an enumerable of the validation values if all of the validations are a success, - /// or the first error message if one of the validations is a failure. - /// - internal static Validation> Sequence(this IEnumerable> validations) => - validations.All(validation => validation.IsSuccess) - ? Validation>.Success(validations.Select(validation => validation.Value)) - : Validation>.Failure(validations.First(validation => validation.IsFailure).ErrorMessage); - - /// - /// Calls the action on the validation value if the validation is a success. - /// - /// The type of the validation's success value. - /// The validation. - /// The action to call if the validation is a success. - internal static void Then(this Validation validation, Action onSuccess) - { - if (validation.IsSuccess) - { - onSuccess(validation.Value); - } - } - } -} diff --git a/src/Simulation/CsharpGeneration/SimulationCode.fs b/src/Simulation/CsharpGeneration/SimulationCode.fs index b41111e373a..917ada6e6b5 100644 --- a/src/Simulation/CsharpGeneration/SimulationCode.fs +++ b/src/Simulation/CsharpGeneration/SimulationCode.fs @@ -1046,6 +1046,22 @@ module SimulationCode = args |> flatOne [] + /// Maps the name and type of each named item in the argument tuple. + let internal mapArgumentTuple mapping context arguments (argumentType : ResolvedType) = + let rec buildTuple = function + | QsTupleItem one -> + match one.VariableName with + | ValidName n -> mapping (n.Value, roslynTypeName context one.Type) :> ExpressionSyntax + | InvalidName -> mapping ("", roslynTypeName context one.Type) :> ExpressionSyntax + | QsTuple many -> + many |> Seq.map buildTuple |> List.ofSeq |> ``tuple`` + if isTuple argumentType.Resolution + then buildTuple arguments + else match flatArgumentsList context arguments with + | [] -> ``ident`` "QVoid" <|.|> ``ident`` "Instance" + | [name, typeName] -> mapping (name, typeName) :> ExpressionSyntax + | flatArgs -> flatArgs |> List.map mapping |> ``tuple`` + let buildRun context className arguments argumentType returnType : MemberDeclarationSyntax = let inType = roslynTypeName context argumentType let outType = roslynTypeName context returnType @@ -1053,24 +1069,9 @@ module SimulationCode = let task = sprintf "System.Threading.Tasks.Task<%s>" outType let flatArgs = arguments |> flatArgumentsList context let opFactoryTypes = [ className; inType; outType ] - - let runArgs = - if (isTuple argumentType.Resolution) then - let rec buildTuple = function - | QsTupleItem one -> - match one.VariableName with - | ValidName n -> ``ident`` n.Value :> ExpressionSyntax - | InvalidName -> ``ident`` "" :> ExpressionSyntax - | QsTuple many -> - many |> Seq.map buildTuple |> List.ofSeq |> ``tuple`` - buildTuple arguments - else - match flatArgs with - | [] -> (``ident`` "QVoid") <|.|> (``ident`` "Instance") - | [(id, _)] -> ``ident`` id :> ExpressionSyntax - | _ -> flatArgs |> List.map (fst >> ``ident``) |> ``tuple`` let uniqueArgName = "__m__" + let runArgs = mapArgumentTuple (fst >> ``ident``) context arguments argumentType let body = [ ``return`` (Some ((``ident`` uniqueArgName) <.> (``generic`` "Run" ``<<`` opFactoryTypes ``>>``, [ runArgs ]))) diff --git a/src/Simulation/EntryPointDriver.Tests/Core.qs b/src/Simulation/EntryPointDriver.Tests/Core.qs new file mode 100644 index 00000000000..8dbddd57b9e --- /dev/null +++ b/src/Simulation/EntryPointDriver.Tests/Core.qs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Core { + @Attribute() + newtype Attribute = Unit; + + @Attribute() + newtype EntryPoint = Unit; +} diff --git a/src/Simulation/EntryPointDriver.Tests/Intrinsic.qs b/src/Simulation/EntryPointDriver.Tests/Intrinsic.qs new file mode 100644 index 00000000000..2684b179893 --- /dev/null +++ b/src/Simulation/EntryPointDriver.Tests/Intrinsic.qs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Quantum.Intrinsic { + operation H (q : Qubit) : Unit { + body intrinsic; + adjoint intrinsic; + controlled intrinsic; + controlled adjoint intrinsic; + } + + operation X (q : Qubit) : Unit { + body intrinsic; + adjoint intrinsic; + controlled intrinsic; + controlled adjoint intrinsic; + } + + operation M (q : Qubit) : Result { + body intrinsic; + } +} diff --git a/src/Simulation/EntryPointDriver.Tests/Program.fs b/src/Simulation/EntryPointDriver.Tests/Program.fs new file mode 100644 index 00000000000..58e40047174 --- /dev/null +++ b/src/Simulation/EntryPointDriver.Tests/Program.fs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +module Program = let [] main _ = 0 diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj b/src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj new file mode 100644 index 00000000000..24ea7c82cda --- /dev/null +++ b/src/Simulation/EntryPointDriver.Tests/Tests.EntryPointDriver.fsproj @@ -0,0 +1,35 @@ + + + + netcoreapp3.1 + false + false + Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs similarity index 63% rename from src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs rename to src/Simulation/EntryPointDriver.Tests/Tests.fs index 0e93aeb32e5..6df1ce5472d 100644 --- a/src/Simulation/CsharpGeneration.Tests/EntryPointTests.fs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -module Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint +module Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests open System open System.Collections.Immutable @@ -24,16 +24,16 @@ open Microsoft.Quantum.Simulation.Simulators /// The path to the Q# file that provides the Microsoft.Quantum.Core namespace. -let private coreFile = Path.Combine ("Circuits", "Core.qs") |> Path.GetFullPath +let private coreFile = Path.GetFullPath "Core.qs" /// The path to the Q# file that provides the Microsoft.Quantum.Intrinsic namespace. -let private intrinsicFile = Path.Combine ("Circuits", "Intrinsic.qs") |> Path.GetFullPath +let private intrinsicFile = Path.GetFullPath "Intrinsic.qs" /// The path to the Q# file that contains the test cases. -let private testFile = Path.Combine ("Circuits", "EntryPointTests.qs") |> Path.GetFullPath +let private testFile = Path.GetFullPath "Tests.qs" /// The namespace used for the test cases. -let private testNamespace = "Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint" +let private testNamespace = "Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests" /// The test case for the given test number. let private testCase = @@ -41,7 +41,7 @@ let private testCase = |> fun text -> text.Split "// ---" |> fun cases num -> cases.[num - 1] -/// Compiles Q# source code into a syntax tree with the list of entry points names. +/// Compiles the Q# source code. let private compileQsharp source = let uri name = Uri ("file://" + name) let fileManager name content = @@ -57,16 +57,17 @@ let private compileQsharp source = compilation.Diagnostics () |> Seq.filter (fun diagnostic -> diagnostic.Severity = DiagnosticSeverity.Error) Assert.Empty errors - compilation.BuiltCompilation.Namespaces, compilation.BuiltCompilation.EntryPoints + compilation.BuiltCompilation -/// Generates C# source code for the given test case number and default simulator. -let private generateCsharp defaultSimulator (syntaxTree : QsNamespace seq, entryPoints) = +/// Generates C# source code from the compiled Q# syntax tree. The given default simulator is set as an assembly +/// constant. +let private generateCsharp defaultSimulator (compilation : QsCompilation) = let assemblyConstants = match defaultSimulator with | Some simulator -> ImmutableDictionary.Empty.Add (AssemblyConstants.DefaultSimulator, simulator) | None -> ImmutableDictionary.Empty - let context = CodegenContext.Create (syntaxTree, assemblyConstants) - let entryPoint = context.allCallables.[Seq.exactlyOne entryPoints] + let context = CodegenContext.Create (compilation, assemblyConstants) + let entryPoint = context.allCallables.[Seq.exactlyOne compilation.EntryPoints] [ SimulationCode.generate (NonNullable<_>.New testFile) context EntryPoint.generate context entryPoint @@ -94,7 +95,9 @@ let private compileCsharp (sources : string seq) = "System.Runtime" "System.Runtime.Extensions" "System.Runtime.Numerics" + "Microsoft.Quantum.CsharpGeneration.EntryPointDriver" "Microsoft.Quantum.QSharp.Core" + "Microsoft.Quantum.QsDataStructures" "Microsoft.Quantum.Runtime.Core" "Microsoft.Quantum.Simulation.Common" "Microsoft.Quantum.Simulation.Simulators" @@ -117,11 +120,11 @@ let private testAssembly testNum defaultSimulator = |> generateCsharp defaultSimulator |> compileCsharp -/// Runs the entry point driver in the assembly with the given command-line arguments, and returns the output, errors, -/// and exit code. +/// Runs the entry point in the assembly with the given command-line arguments, and returns the output, errors, and exit +/// code. let private run (assembly : Assembly) (args : string[]) = - let driver = assembly.GetType (EntryPoint.generatedNamespace testNamespace + ".Driver") - let main = driver.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) + let entryPoint = assembly.GetType (sprintf "%s.%s" testNamespace EntryPoint.entryPointClassName) + let main = entryPoint.GetMethod("Main", BindingFlags.NonPublic ||| BindingFlags.Static) let previousCulture = CultureInfo.DefaultThreadCurrentCulture let previousOut = Console.Out let previousError = Console.Error @@ -139,19 +142,28 @@ let private run (assembly : Assembly) (args : string[]) = Console.SetOut previousOut CultureInfo.DefaultThreadCurrentCulture <- previousCulture +/// Replaces every sequence of whitespace characters in the string with a single space. +let private normalize s = Regex.Replace(s, @"\s+", " ").Trim() + /// Asserts that running the entry point in the assembly with the given arguments succeeds and yields the expected -/// output. +/// output. The standard error and out streams of the actual output are concatenated in that order. let private yields expected (assembly, args) = - let normalize text = Regex.Replace(text, @"\s+", " ").Trim() let out, error, exitCode = run assembly args Assert.True (0 = exitCode, sprintf "Expected exit code 0, but got %d with:\n\n%s\n\n%s" exitCode out error) - Assert.Equal (normalize expected, normalize out) + Assert.Equal (normalize expected, normalize (error + out)) /// Asserts that running the entry point in the assembly with the given arguments fails. let private fails (assembly, args) = let out, error, exitCode = run assembly args Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) +/// Asserts that running the entry point in the assembly with the given arguments fails and the error message starts +/// with the expected message. +let private failsWith expected (assembly, args) = + let out, error, exitCode = run assembly args + Assert.True (0 <> exitCode, sprintf "Expected non-zero exit code, but got 0 with:\n\n%s\n\n%s" out error) + Assert.StartsWith (normalize expected, normalize error) + /// A tuple of the test assembly and arguments using the standard default simulator. The tuple can be passed to yields /// or fails. let private test testNum = @@ -212,10 +224,10 @@ let ``Accepts Double`` () = [] let ``Accepts Bool`` () = let given = test 7 - given ["-b"] |> yields "True" given ["-b"; "false"] |> yields "False" given ["-b"; "true"] |> yields "True" given ["-b"; "one"] |> fails + given ["-b"] |> fails [] let ``Accepts Pauli`` () = @@ -360,17 +372,40 @@ let ``Requires all options`` () = given [] |> fails +// Tuples + +[] +let ``Accepts redundant one-tuple`` () = + let given = test 21 + given ["-x"; "7"] |> yields "7" + +[] +let ``Accepts redundant two-tuple`` () = + let given = test 22 + given ["-x"; "7"; "-y"; "8"] |> yields "7 8" + +[] +let ``Accepts one-tuple`` () = + let given = test 23 + given ["-x"; "7"; "-y"; "8"] |> yields "7 8" + +[] +let ``Accepts two-tuple`` () = + let given = test 24 + given ["-x"; "7"; "-y"; "8"; "-z"; "9"] |> yields "7 8 9" + + // Name Conversion [] let ``Uses kebab-case`` () = - let given = test 21 + let given = test 25 given ["--camel-case-name"; "foo"] |> yields "foo" given ["--camelCaseName"; "foo"] |> fails [] let ``Uses single-dash short names`` () = - let given = test 22 + let given = test 26 given ["-x"; "foo"] |> yields "foo" given ["--x"; "foo"] |> fails @@ -379,64 +414,70 @@ let ``Uses single-dash short names`` () = [] let ``Shadows --simulator`` () = - let given = test 23 - given ["--simulator"; "foo"] |> yields "foo" - given ["--simulator"; AssemblyConstants.ResourcesEstimator] |> yields AssemblyConstants.ResourcesEstimator + let given = test 27 + given ["--simulator"; "foo"] + |> yields "Warning: Option --simulator is overridden by an entry point parameter name. Using default value QuantumSimulator. + foo" + given ["--simulator"; AssemblyConstants.ResourcesEstimator] + |> yields (sprintf "Warning: Option --simulator is overridden by an entry point parameter name. Using default value QuantumSimulator. + %s" + AssemblyConstants.ResourcesEstimator) given ["-s"; AssemblyConstants.ResourcesEstimator; "--simulator"; "foo"] |> fails given ["-s"; "foo"] |> fails [] let ``Shadows -s`` () = - let given = test 24 + let given = test 28 given ["-s"; "foo"] |> yields "foo" given ["--simulator"; AssemblyConstants.ToffoliSimulator; "-s"; "foo"] |> yields "foo" given ["--simulator"; "bar"; "-s"; "foo"] |> fails [] let ``Shadows --version`` () = - let given = test 25 + let given = test 29 given ["--version"; "foo"] |> yields "foo" // Simulators // The expected output from the resources estimator. -let private resourceSummary = "Metric Sum -CNOT 0 -QubitClifford 1 -R 0 -Measure 1 -T 0 -Depth 0 -Width 1 -BorrowedWidth 0" +let private resourceSummary = + "Metric Sum + CNOT 0 + QubitClifford 1 + R 0 + Measure 1 + T 0 + Depth 0 + Width 1 + BorrowedWidth 0" [] let ``Supports QuantumSimulator`` () = - let given = test 26 + let given = test 30 given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "true"] |> yields "Hello, World!" [] let ``Supports ToffoliSimulator`` () = - let given = test 26 + let given = test 30 given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "false"] |> yields "Hello, World!" given ["--simulator"; AssemblyConstants.ToffoliSimulator; "--use-h"; "true"] |> fails [] let ``Supports ResourcesEstimator`` () = - let given = test 26 + let given = test 30 given ["--simulator"; AssemblyConstants.ResourcesEstimator; "--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.ResourcesEstimator; "--use-h"; "true"] |> yields resourceSummary [] let ``Rejects unknown simulator`` () = - let given = test 26 + let given = test 30 given ["--simulator"; "FooSimulator"; "--use-h"; "false"] |> fails [] let ``Supports default standard simulator`` () = - let given = testWith 26 AssemblyConstants.ResourcesEstimator + let given = testWith 30 AssemblyConstants.ResourcesEstimator given ["--use-h"; "false"] |> yields resourceSummary given ["--simulator"; AssemblyConstants.QuantumSimulator; "--use-h"; "false"] |> yields "Hello, World!" @@ -444,7 +485,7 @@ let ``Supports default standard simulator`` () = let ``Supports default custom simulator`` () = // This is not really a "custom" simulator, but the driver does not recognize the fully-qualified name of the // standard simulators, so it is treated as one. - let given = testWith 26 typeof.FullName + let given = testWith 30 typeof.FullName given ["--use-h"; "false"] |> yields "Hello, World!" given ["--use-h"; "true"] |> fails given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> yields "Hello, World!" @@ -455,29 +496,141 @@ let ``Supports default custom simulator`` () = given ["--simulator"; typeof.FullName; "--use-h"; "false"] |> fails -// Help +// Azure Quantum Submission + +/// Standard command-line arguments for the "submit" command without specifying a target. +let private submitWithoutTarget = + [ "submit" + "--storage" + "myStorage" + "--subscription" + "mySubscription" + "--resource-group" + "myResourceGroup" + "--workspace" + "myWorkspace" ] + +/// Standard command-line arguments for the "submit" command using the "nothing" target. +let private submitWithTestTarget = submitWithoutTarget @ ["--target"; "nothing"] [] -let ``Uses documentation`` () = - let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) - let message = (name, name) ||> sprintf "%s: - This test checks that the entry point documentation appears correctly in the command line help message. +let ``Submit can submit a job`` () = + let given = test 1 + given submitWithTestTarget + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit can show only the ID`` () = + let given = test 1 + given (submitWithTestTarget @ ["--output"; "id"]) |> yields "00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit uses default values`` () = + let given = test 1 + given (submitWithTestTarget @ ["--verbose"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + Target: nothing + Storage: myStorage + Subscription: mySubscription + Resource Group: myResourceGroup + Workspace: myWorkspace + AAD Token: + Base URI: + Shots: 500 + Output: FriendlyUri + Dry Run: False + Verbose: True + + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit allows overriding default values`` () = + let given = test 1 + given (submitWithTestTarget @ ["--verbose"; "--aad-token"; "myToken"; "--base-uri"; "myBaseUri"; "--shots"; "750"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + Target: nothing + Storage: myStorage + Subscription: mySubscription + Resource Group: myResourceGroup + Workspace: myWorkspace + AAD Token: myToken + Base URI: myBaseUri + Shots: 750 + Output: FriendlyUri + Dry Run: False + Verbose: True + + 00000000-0000-0000-0000-0000000000000" + +[] +let ``Submit requires a positive number of shots`` () = + let given = test 1 + given (submitWithTestTarget @ ["--shots"; "1"]) + |> yields "The friendly URI for viewing job results is not available yet. Showing the job ID instead. + 00000000-0000-0000-0000-0000000000000" + given (submitWithTestTarget @ ["--shots"; "0"]) |> fails + given (submitWithTestTarget @ ["--shots"; "-1"]) |> fails -Usage: - %s [options] [command] +[] +let ``Submit fails with unknown target`` () = + let given = test 1 + given (submitWithoutTarget @ ["--target"; "foo"]) |> failsWith "The target 'foo' was not recognized." -Options: - -n (REQUIRED) A number. - --pauli (REQUIRED) The name of a Pauli matrix. - --my-cool-bool (REQUIRED) A neat bit. - -s, --simulator The name of the simulator to use. - --version Show version information - -?, -h, --help Show help and usage information -Commands: - simulate (default) Run the program using a local simulator." +// Help - let given = test 27 +[] +let ``Uses documentation`` () = + let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) + let message = + (name, name) + ||> sprintf "%s: + This test checks that the entry point documentation appears correctly in the command line help message. + + Usage: + %s [options] [command] + + Options: + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. + -s, --simulator The name of the simulator to use. + --version Show version information + -?, -h, --help Show help and usage information + + Commands: + simulate (default) Run the program using a local simulator." + + let given = test 31 given ["--help"] |> yields message given ["-h"] |> yields message given ["-?"] |> yields message + +[] +let ``Shows help text for submit command`` () = + let name = Path.GetFileNameWithoutExtension (Assembly.GetEntryAssembly().Location) + let message = + name + |> sprintf "Usage: + %s submit [options] + + Options: + --target (REQUIRED) The target device ID. + --storage (REQUIRED) The storage account connection string. + --subscription (REQUIRED) The subscription ID. + --resource-group (REQUIRED) The resource group name. + --workspace (REQUIRED) The workspace name. + --aad-token The Azure Active Directory authentication token. + --base-uri The base URI of the Azure Quantum endpoint. + --output The information to show in the output after the job is submitted. + --shots The number of times the program is executed on the target machine. + --dry-run Validate the program and options, but do not submit to Azure Quantum. + --verbose Show additional information about the submission. + -n (REQUIRED) A number. + --pauli (REQUIRED) The name of a Pauli matrix. + --my-cool-bool (REQUIRED) A neat bit. + -?, -h, --help Show help and usage information" + + let given = test 31 + given ["submit"; "--help"] |> yields message diff --git a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs b/src/Simulation/EntryPointDriver.Tests/Tests.qs similarity index 57% rename from src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs rename to src/Simulation/EntryPointDriver.Tests/Tests.qs index 4f12bc51f3a..f45080104db 100644 --- a/src/Simulation/CsharpGeneration.Tests/Circuits/EntryPointTests.qs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.qs @@ -5,14 +5,14 @@ // No Options // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ReturnUnit() : Unit { } } // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ReturnInt() : Int { return 42; @@ -21,7 +21,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ReturnString() : String { return "Hello, World!"; @@ -34,7 +34,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // Single Option // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptInt(n : Int) : Int { return n; @@ -43,7 +43,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptBigInt(n : BigInt) : BigInt { return n; @@ -52,7 +52,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptDouble(n : Double) : Double { return n; @@ -61,7 +61,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptBool(b : Bool) : Bool { return b; @@ -70,7 +70,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptPauli(p : Pauli) : Pauli { return p; @@ -79,7 +79,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptResult(r : Result) : Result { return r; @@ -88,7 +88,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptRange(r : Range) : Range { return r; @@ -97,7 +97,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptString(s : String) : String { return s; @@ -106,7 +106,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptUnit(u : Unit) : Unit { return u; @@ -115,7 +115,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptStringArray(xs : String[]) : String[] { return xs; @@ -124,7 +124,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptBigIntArray(bs : BigInt[]) : BigInt[] { return bs; @@ -133,7 +133,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptPauliArray(ps : Pauli[]) : Pauli[] { return ps; @@ -142,7 +142,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptRangeArray(rs : Range[]) : Range[] { return rs; @@ -151,7 +151,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptResultArray(rs : Result[]) : Result[] { return rs; @@ -160,7 +160,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation AcceptUnitArray(us : Unit[]) : Unit[] { return us; @@ -173,7 +173,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // Multiple Options // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation TwoOptions(n : Int, b : Bool) : String { return $"{n} {b}"; @@ -182,7 +182,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ThreeOptions(n : Int, b : Bool, xs : String[]) : String { return $"{n} {b} {xs}"; @@ -191,11 +191,51 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- +// +// Tuples +// + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { + @EntryPoint() + operation RedundantOneTuple((x : Int)) : String { + return $"{x}"; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { + @EntryPoint() + operation RedundantTwoTuple((x : Int, y : Int)) : String { + return $"{x} {y}"; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { + @EntryPoint() + operation OneTuple(x : Int, (y : Int)) : String { + return $"{x} {y}"; + } +} + +// --- + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { + @EntryPoint() + operation TwoTuple(x : Int, (y : Int, z : Int)) : String { + return $"{x} {y} {z}"; + } +} + +// --- + // // Name Conversion // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation CamelCase(camelCaseName : String) : String { return camelCaseName; @@ -204,7 +244,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation SingleLetter(x : String) : String { return x; @@ -217,7 +257,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // Shadowing // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ShadowSimulator(simulator : String) : String { return simulator; @@ -226,7 +266,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ShadowS(s : String) : String { return s; @@ -235,7 +275,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // --- -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { @EntryPoint() operation ShadowVersion(version : String) : String { return version; @@ -248,7 +288,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // Simulators // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { open Microsoft.Quantum.Intrinsic; @EntryPoint() @@ -274,7 +314,7 @@ namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { // Help // -namespace Microsoft.Quantum.CsharpGeneration.Testing.EntryPoint { +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Tests { /// # Summary /// This test checks that the entry point documentation appears correctly in the command line help message. /// diff --git a/src/Simulation/EntryPointDriver/Azure.cs b/src/Simulation/EntryPointDriver/Azure.cs new file mode 100644 index 00000000000..40a0ad01e44 --- /dev/null +++ b/src/Simulation/EntryPointDriver/Azure.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.CommandLine.Parsing; +using System.Threading.Tasks; +using Microsoft.Azure.Quantum; +using Microsoft.Azure.Quantum.Exceptions; +using Microsoft.Quantum.Runtime; +using Microsoft.Quantum.Simulation.Common.Exceptions; +using static Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Driver; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// Provides entry point submission to Azure Quantum. + /// + internal static class Azure + { + /// + /// Submits the entry point to Azure Quantum. + /// + /// The entry point. + /// The command-line parsing result. + /// The submission settings. + /// The entry point's argument type. + /// The entry point's return type. + internal static async Task Submit( + IEntryPoint entryPoint, ParseResult parseResult, AzureSettings settings) + { + if (settings.Verbose) + { + Console.WriteLine(settings); + Console.WriteLine(); + } + + var machine = CreateMachine(settings); + if (machine is null) + { + DisplayWithColor(ConsoleColor.Red, Console.Error, + $"The target '{settings.Target}' was not recognized."); + return 1; + } + + var input = entryPoint.CreateArgument(parseResult); + if (settings.DryRun) + { + var (isValid, message) = machine.Validate(entryPoint.Info, input); + Console.WriteLine(isValid ? "✔️ The program is valid!" : "❌ The program is invalid."); + if (!string.IsNullOrWhiteSpace(message)) + { + Console.WriteLine(); + Console.WriteLine(message); + } + return isValid ? 0 : 1; + } + else + { + try + { + var job = await machine.SubmitAsync( + entryPoint.Info, input, new SubmissionContext { Shots = settings.Shots }); + DisplayJob(job, settings.Output); + } + catch (AzureQuantumException azureQuantumEx) + { + DisplayWithColor( + ConsoleColor.Red, + Console.Error, + "Something went wrong when submitting the program to the Azure Quantum service."); + + Console.Error.WriteLine(); + Console.Error.WriteLine(azureQuantumEx.Message); + return 1; + } + catch (QuantumProcessorTranslationException translationEx) + { + DisplayWithColor( + ConsoleColor.Red, + Console.Error, + "Something went wrong when performing translation to the intermediate representation used by the target quantum machine."); + + Console.Error.WriteLine(); + Console.Error.WriteLine(translationEx.Message); + return 1; + } + + return 0; + } + } + + /// + /// Displays the job using the output format. + /// + /// The job. + /// The output format. + /// Thrown if the output format is invalid. + private static void DisplayJob(IQuantumMachineJob job, OutputFormat format) + { + switch (format) + { + case OutputFormat.FriendlyUri: + // TODO: + DisplayWithColor(ConsoleColor.Yellow, Console.Error, + "The friendly URI for viewing job results is not available yet. Showing the job ID instead."); + Console.WriteLine(job.Id); + break; + case OutputFormat.Id: + Console.WriteLine(job.Id); + break; + default: + throw new ArgumentOutOfRangeException($"Invalid output format '{format}'."); + } + } + + /// + /// Creates a quantum machine based on the Azure Quantum submission settings. + /// + /// The Azure Quantum submission settings. + /// A quantum machine. + private static IQuantumMachine? CreateMachine(AzureSettings settings) => + settings.Target == "nothing" + ? new NothingMachine() + : QuantumMachineFactory.CreateMachine(settings.CreateWorkspace(), settings.Target, settings.Storage); + + /// + /// The quantum machine submission context. + /// + private sealed class SubmissionContext : IQuantumMachineSubmissionContext + { + public string? FriendlyName { get; set; } + + public int Shots { get; set; } + } + } + + /// + /// The information to show in the output after the job is submitted. + /// + internal enum OutputFormat + { + /// + /// Show a friendly message with a URI that can be used to see the job results. + /// + FriendlyUri, + + /// + /// Show only the job ID. + /// + Id + } + + /// + /// Settings for a submission to Azure Quantum. + /// + internal sealed class AzureSettings + { + /// + /// The target device ID. + /// + public string? Target { get; set; } + + /// + /// The storage account connection string. + /// + public string? Storage { get; set; } + + /// + /// The subscription ID. + /// + public string? Subscription { get; set; } + + /// + /// The resource group name. + /// + public string? ResourceGroup { get; set; } + + /// + /// The workspace name. + /// + public string? Workspace { get; set; } + + /// + /// The Azure Active Directory authentication token. + /// + public string? AadToken { get; set; } + + /// + /// The base URI of the Azure Quantum endpoint. + /// + public Uri? BaseUri { get; set; } + + /// + /// The number of times the program is executed on the target machine. + /// + public int Shots { get; set; } + + /// + /// The information to show in the output after the job is submitted. + /// + public OutputFormat Output { get; set; } + + /// + /// Validate the program and options, but do not submit to Azure Quantum. + /// + public bool DryRun { get; set; } + + /// + /// Show additional information about the submission. + /// + public bool Verbose { get; set; } + + /// + /// Creates a based on the settings. + /// + /// The based on the settings. + internal Workspace CreateWorkspace() => + AadToken is null + ? new Workspace(Subscription, ResourceGroup, Workspace, baseUri: BaseUri) + : new Workspace(Subscription, ResourceGroup, Workspace, AadToken, BaseUri); + + public override string ToString() => + string.Join(System.Environment.NewLine, + $"Target: {Target}", + $"Storage: {Storage}", + $"Subscription: {Subscription}", + $"Resource Group: {ResourceGroup}", + $"Workspace: {Workspace}", + $"AAD Token: {AadToken}", + $"Base URI: {BaseUri}", + $"Shots: {Shots}", + $"Output: {Output}", + $"Dry Run: {DryRun}", + $"Verbose: {Verbose}"); + } +} diff --git a/src/Simulation/EntryPointDriver/Driver.cs b/src/Simulation/EntryPointDriver/Driver.cs new file mode 100644 index 00000000000..9bca59fe327 --- /dev/null +++ b/src/Simulation/EntryPointDriver/Driver.cs @@ -0,0 +1,312 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Help; +using System.CommandLine.Invocation; +using System.CommandLine.Parsing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Quantum.QsCompiler.ReservedKeywords; +using Microsoft.Quantum.Simulation.Core; +using static Microsoft.Quantum.CsharpGeneration.EntryPointDriver.Driver; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// The entry point driver is the entry point for the C# application that executes the Q# entry point. + /// + /// The entry point's callable type. + /// The entry point's argument type. + /// The entry point's return type. + public sealed class Driver where TCallable : AbstractCallable, ICallable + { + /// + /// The entry point. + /// + private readonly IEntryPoint entryPoint; + + /// + /// The simulator option. + /// + private OptionInfo SimulatorOption { get; } + + /// + /// Creates a new driver for the entry point. + /// + /// The entry point. + public Driver(IEntryPoint entryPoint) + { + this.entryPoint = entryPoint; + SimulatorOption = new OptionInfo( + new[] + { + "--" + CommandLineArguments.SimulatorOption.Item1, + "-" + CommandLineArguments.SimulatorOption.Item2 + }, + entryPoint.DefaultSimulator, + "The name of the simulator to use.", + suggestions: new[] + { + AssemblyConstants.QuantumSimulator, + AssemblyConstants.ToffoliSimulator, + AssemblyConstants.ResourcesEstimator, + entryPoint.DefaultSimulator + }); + } + + /// + /// Runs the entry point using the command-line arguments. + /// + /// The command-line arguments. + /// The exit code. + public async Task Run(string[] args) + { + var simulate = new Command("simulate", "(default) Run the program using a local simulator.") + { + Handler = CommandHandler.Create(Simulate) + }; + AddOptionIfAvailable(simulate, SimulatorOption); + + var submit = new Command("submit", "Submit the program to Azure Quantum.") + { + IsHidden = true, + Handler = CommandHandler.Create(Submit) + }; + AddOptionIfAvailable(submit, TargetOption); + AddOptionIfAvailable(submit, StorageOption); + AddOptionIfAvailable(submit, SubscriptionOption); + AddOptionIfAvailable(submit, ResourceGroupOption); + AddOptionIfAvailable(submit, WorkspaceOption); + AddOptionIfAvailable(submit, AadTokenOption); + AddOptionIfAvailable(submit, BaseUriOption); + AddOptionIfAvailable(submit, OutputOption); + AddOptionIfAvailable(submit, ShotsOption); + AddOptionIfAvailable(submit, DryRunOption); + AddOptionIfAvailable(submit, VerboseOption); + + var root = new RootCommand(entryPoint.Summary) { simulate, submit }; + foreach (var option in entryPoint.Options) + { + root.AddGlobalOption(option); + } + + // Set the simulate command as the default. + foreach (var option in simulate.Options) + { + root.AddOption(option); + } + root.Handler = simulate.Handler; + + Console.OutputEncoding = Encoding.UTF8; + return await new CommandLineBuilder(root) + .UseDefaults() + .UseHelpBuilder(context => new QsHelpBuilder(context.Console)) + .Build() + .InvokeAsync(args); + } + + /// + /// Simulates the entry point. + /// + /// The command-line parsing result. + /// The simulator to use. + /// The exit code. + private async Task Simulate(ParseResult parseResult, string simulator) => + await Simulation.Simulate( + entryPoint, parseResult, DefaultIfShadowed(SimulatorOption, simulator)); + + /// + /// Submits the entry point to Azure Quantum. + /// + /// The command-line parsing result. + /// The submission settings. + private async Task Submit(ParseResult parseResult, AzureSettings settings) => + await Azure.Submit(entryPoint, parseResult, new AzureSettings + { + Target = settings.Target, + Storage = settings.Storage, + Subscription = settings.Subscription, + ResourceGroup = settings.ResourceGroup, + Workspace = settings.Workspace, + AadToken = DefaultIfShadowed(AadTokenOption, settings.AadToken), + BaseUri = DefaultIfShadowed(BaseUriOption, settings.BaseUri), + Shots = DefaultIfShadowed(ShotsOption, settings.Shots), + Output = DefaultIfShadowed(OutputOption, settings.Output), + DryRun = DefaultIfShadowed(DryRunOption, settings.DryRun), + Verbose = DefaultIfShadowed(VerboseOption, settings.Verbose) + }); + + /// + /// Returns true if the alias is not already used by an entry point option. + /// + /// The alias to check. + /// True if the alias is available for use by the driver. + private bool IsAliasAvailable(string alias) => + !entryPoint.Options.SelectMany(option => option.RawAliases).Contains(alias); + + /// + /// Returns the default value and displays a warning if the primary (first) alias is shadowed by an entry point + /// option, and returns the original value otherwise. + /// + /// The type of the option values. + /// The option. + /// The value of the option given on the command line. + /// The default value or the original value. + private T DefaultIfShadowed(OptionInfo option, T value) + { + if (IsAliasAvailable(option.Aliases.First())) + { + return value; + } + else + { + DisplayWithColor(ConsoleColor.Yellow, Console.Error, + $"Warning: Option {option.Aliases.First()} is overridden by an entry point parameter name. " + + $"Using default value {option.DefaultValue}."); + return option.DefaultValue; + } + } + + /// + /// Adds the option to the command using only the aliases that are available, and only if the primary (first) + /// alias is available. If a required option is not available, the command is disabled. + /// + /// The command to add the option to. + /// The option to add. + /// The type of the option's argument. + private void AddOptionIfAvailable(Command command, OptionInfo option) + { + if (IsAliasAvailable(option.Aliases.First())) + { + command.AddOption(option.Create(option.Aliases.Where(IsAliasAvailable))); + } + else if (option.Required) + { + command.AddValidator(commandResult => + $"The required option {option.Aliases.First()} conflicts with an entry point parameter name."); + } + } + } + + /// + /// Static members for . + /// + internal static class Driver + { + // TODO: Define the aliases as constants. + + /// + /// The target option. + /// + internal static readonly OptionInfo TargetOption = new OptionInfo( + new[] { "--target" }, "The target device ID."); + + /// + /// The storage option. + /// + internal static readonly OptionInfo StorageOption = new OptionInfo( + new[] { "--storage" }, "The storage account connection string."); + + /// + /// The subscription option. + /// + internal static readonly OptionInfo SubscriptionOption = new OptionInfo( + new[] { "--subscription" }, "The subscription ID."); + + /// + /// The resource group option. + /// + internal static readonly OptionInfo ResourceGroupOption = new OptionInfo( + new[] { "--resource-group" }, "The resource group name."); + + /// + /// The workspace option. + /// + internal static readonly OptionInfo WorkspaceOption = new OptionInfo( + new[] { "--workspace" }, "The workspace name."); + + /// + /// The AAD token option. + /// + internal static readonly OptionInfo AadTokenOption = new OptionInfo( + new[] { "--aad-token" }, default, "The Azure Active Directory authentication token."); + + /// + /// The base URI option. + /// + internal static readonly OptionInfo BaseUriOption = new OptionInfo( + new[] { "--base-uri" }, default, "The base URI of the Azure Quantum endpoint."); + + /// + /// The shots option. + /// + internal static readonly OptionInfo ShotsOption = new OptionInfo( + new[] { "--shots" }, + 500, + "The number of times the program is executed on the target machine.", + validator: result => + int.TryParse(result.Tokens.SingleOrDefault()?.Value, out var value) && value <= 0 + ? "The number of shots must be a positive number." + : default); + + /// + /// The output option. + /// + internal static readonly OptionInfo OutputOption = new OptionInfo( + new[] { "--output" }, + OutputFormat.FriendlyUri, + "The information to show in the output after the job is submitted."); + + /// + /// The dry run option. + /// + internal static readonly OptionInfo DryRunOption = new OptionInfo( + new[] { "--dry-run" }, + false, + "Validate the program and options, but do not submit to Azure Quantum."); + + /// + /// The verbose option. + /// + internal static readonly OptionInfo VerboseOption = new OptionInfo( + new[] { "--verbose" }, false, "Show additional information about the submission."); + + /// + /// Displays a message to the console using the given color and text writer. + /// + /// The text color. + /// The text writer for the console output stream. + /// The message to display. + internal static void DisplayWithColor(ConsoleColor color, TextWriter writer, string message) + { + var originalForeground = Console.ForegroundColor; + Console.ForegroundColor = color; + writer.WriteLine(message); + Console.ForegroundColor = originalForeground; + } + } + + /// + /// A modification of the command-line class. + /// + internal sealed class QsHelpBuilder : HelpBuilder + { + /// + /// Creates a new help builder using the given console. + /// + /// The console to use. + internal QsHelpBuilder(IConsole console) : base(console) { } + + protected override string ArgumentDescriptor(IArgument argument) + { + // Hide long argument descriptors. + var descriptor = base.ArgumentDescriptor(argument); + return descriptor.Length > 30 ? argument.Name : descriptor; + } + } +} diff --git a/src/Simulation/EntryPointDriver/EntryPointDriver.csproj b/src/Simulation/EntryPointDriver/EntryPointDriver.csproj new file mode 100644 index 00000000000..7f04ac0ba36 --- /dev/null +++ b/src/Simulation/EntryPointDriver/EntryPointDriver.csproj @@ -0,0 +1,22 @@ + + + + + + netstandard2.1 + Microsoft.Quantum.CsharpGeneration.EntryPointDriver + Microsoft.Quantum.CsharpGeneration.EntryPointDriver + enable + + + + + + + + + + + + + diff --git a/src/Simulation/EntryPointDriver/IEntryPoint.cs b/src/Simulation/EntryPointDriver/IEntryPoint.cs new file mode 100644 index 00000000000..65250d086d3 --- /dev/null +++ b/src/Simulation/EntryPointDriver/IEntryPoint.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; +using Microsoft.Quantum.Simulation.Core; + +namespace Microsoft.Quantum.CsharpGeneration.EntryPointDriver +{ + /// + /// The interface between the entry point and the command-line program. + /// + /// + /// Contains entry point properties needed by the command-line interface and allows the entry point to use + /// command-line arguments. The implementation of this interface is code-generated. + /// + /// The entry point's argument type. + /// The entry point's return type. + public interface IEntryPoint + { + /// + /// The summary from the entry point's documentation comment. + /// + string Summary { get; } + + /// + /// The command-line options corresponding to the entry point's parameters. + /// + IEnumerable