diff --git a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs index 9f8a6935233..ecca60b212c 100644 --- a/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs +++ b/src/Azure/Azure.Quantum.Client.Test/WorkspaceTest.cs @@ -7,10 +7,12 @@ using System.IO.Compression; using System.Linq; using System.Net; +using System.Reflection; using System.Threading; using System.Threading.Tasks; - +using Azure.Identity; using Azure.Quantum; +using Azure.Quantum.Jobs; using Azure.Quantum.Jobs.Models; using Microsoft.Azure.Quantum.Exceptions; @@ -176,6 +178,100 @@ public async Task ListProviderStatusTest() Assert.AreEqual(0, max); } + [TestMethod] + [TestCategory("Local")] + public async Task ApplicationIdTest() + { + const string ENV_VAR_APPID = "EnvVarAppId"; + const string OPTIONS_APPID = "OptionAppId"; + const string LONG_ENV_VAR_APPID = "LongEnvVarAppId"; + const string LONG_OPTIONS_APPID = "LongOptionAppId"; + const string VERY_LONG_ENV_VAR_APPID = "VeryVeryVeryVeryVeryVeryLongEnvVarAppId"; + const string VERY_LONG_OPTIONS_APPID = "VeryVeryVeryVeryVeryVeryLongOptionAppId"; + const string APPID_ENV_VAR_NAME = "AZURE_QUANTUM_NET_APPID"; + + Func createWorkspace = (QuantumJobClientOptions options) => + { + var credential = new ClientSecretCredential(tenantId: "72f988bf-86f1-41af-91ab-2d7cd011db47", + clientId: "00000000-0000-0000-0000-000000000000", + clientSecret: "PLACEHOLDER"); + return new Workspace(subscriptionId: "SubscriptionId", + resourceGroupName: "ResourceGroupName", + workspaceName: "WorkspaceName", + location: "WestUs", + options: options, + credential: credential); + }; + + var originalEnvironmentAppId = Environment.GetEnvironmentVariable(APPID_ENV_VAR_NAME); + try + { + + // Test with no Environment AppId and no Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, null); + var workspace = createWorkspace(null); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + Assert.AreEqual("", workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with Environment AppId and no Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, ENV_VAR_APPID); + workspace = createWorkspace(null); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + Assert.AreEqual(ENV_VAR_APPID, workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with no Environment AppId and with Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, null); + var options = new QuantumJobClientOptions(); + options.Diagnostics.ApplicationId = OPTIONS_APPID; + workspace = createWorkspace(options); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + Assert.AreEqual(OPTIONS_APPID, workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with Environment AppId and with Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, ENV_VAR_APPID); + options = new QuantumJobClientOptions(); + options.Diagnostics.ApplicationId = OPTIONS_APPID; + workspace = createWorkspace(options); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + Assert.AreEqual($"{OPTIONS_APPID}-{ENV_VAR_APPID}", workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with long (>24 chars) combination of Environment AppId and Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, LONG_ENV_VAR_APPID); + options = new QuantumJobClientOptions(); + options.Diagnostics.ApplicationId = LONG_OPTIONS_APPID; + workspace = createWorkspace(options); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + var truncatedAppId = $"{LONG_OPTIONS_APPID}-{LONG_ENV_VAR_APPID}".Substring(0, 24); + Assert.AreEqual(truncatedAppId, workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with long (>24 chars) Environment AppId and no Options AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, VERY_LONG_ENV_VAR_APPID); + workspace = createWorkspace(null); + Assert.IsNotNull(workspace.ClientOptions); + Assert.IsNotNull(workspace.ClientOptions.Diagnostics); + Assert.AreEqual(VERY_LONG_ENV_VAR_APPID.Substring(0, 24), workspace.ClientOptions.Diagnostics.ApplicationId); + + // Test with long (>24 chars) Options AppId and no Environment AppId + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, null); + options = new QuantumJobClientOptions(); + Assert.ThrowsException(() => + options.Diagnostics.ApplicationId = VERY_LONG_OPTIONS_APPID); + } + finally + { + // restore original env var AZURE_QUANTUM_NET_APPID + if (originalEnvironmentAppId != null) + { + Environment.SetEnvironmentVariable(APPID_ENV_VAR_NAME, originalEnvironmentAppId); + } + } + } + private static void AssertJob(CloudJob job) { Assert.IsNotNull(job); diff --git a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs index e4015ee75e8..e431b2fd8ca 100644 --- a/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs +++ b/src/Azure/Azure.Quantum.Client/JobManagement/Workspace.cs @@ -10,7 +10,6 @@ namespace Microsoft.Azure.Quantum using System.Threading; using System.Threading.Tasks; using global::Azure.Core; - using global::Azure.Identity; using global::Azure.Quantum; using global::Azure.Quantum.Jobs; using global::Azure.Quantum.Jobs.Models; @@ -50,9 +49,10 @@ public Workspace( // Optional parameters: credential ??= CredentialFactory.CreateCredential(CredentialType.Default, subscriptionId); - options ??= new QuantumJobClientOptions(); - options.Diagnostics.ApplicationId = options.Diagnostics.ApplicationId - ?? Environment.GetEnvironmentVariable("AZURE_QUANTUM_NET_APPID"); + + // Make sure use the property Setter as we have some logic + // tto apply here + this.ClientOptions = options; this.ResourceGroupName = resourceGroupName; this.WorkspaceName = workspaceName; @@ -65,7 +65,7 @@ public Workspace( workspaceName, location, credential, - options); + this.ClientOptions); } public string ResourceGroupName { get; } @@ -81,6 +81,34 @@ public Workspace( /// public QuantumJobClient Client { get; } + /// + /// The options used to create the client to communicate with the service. + /// + public QuantumJobClientOptions ClientOptions + { + get => this.clientOptions; + set + { + // Set the ApplicationId that will be added as a UserAgent prefix + // in calls to the Azure Quantum API. + var applicationId = string.Join('-', + value?.Diagnostics?.ApplicationId?.Trim(), + Environment.GetEnvironmentVariable("AZURE_QUANTUM_NET_APPID")?.Trim() + )?.Trim('-', ' '); + if (applicationId?.Length > 24) + { + applicationId = applicationId?.Substring(0, 24); + } + + value ??= new QuantumJobClientOptions(); + value.Diagnostics.ApplicationId = applicationId; + + this.clientOptions = value; + } + } + + private QuantumJobClientOptions clientOptions; + /// /// Submits the job. /// diff --git a/src/Simulation/EntryPointDriver.Tests/Tests.fs b/src/Simulation/EntryPointDriver.Tests/Tests.fs index 58037e52f0e..1e00e8bd15b 100644 --- a/src/Simulation/EntryPointDriver.Tests/Tests.fs +++ b/src/Simulation/EntryPointDriver.Tests/Tests.fs @@ -646,7 +646,7 @@ let ``Submit allows overriding default values`` () = https://www.example.com/00000000-0000-0000-0000-0000000000000" - + [] let ``Submit allows a long user-agent`` () = let given = test "Returns Unit" @@ -661,7 +661,7 @@ let ``Submit allows a long user-agent`` () = "--shots" "750" "--user-agent" - "a-very-long-user-agent-(it-will-be-truncated)" + "a-very-long-user-agent-(it-will-be-not-be-truncated-here)" "--credential" "cli" ]) @@ -674,18 +674,17 @@ let ``Submit allows a long user-agent`` () = Location: myLocation Credential: CLI AadToken: myTok - UserAgent: a-very-long-user-agent-( + UserAgent: a-very-long-user-agent-(it-will-be-not-be-truncated-here) Job Name: myJobName Job Parameters: Shots: 750 Output: FriendlyUri Dry Run: False Verbose: True - Submitting Q# entry point using a quantum machine. - https://www.example.com/00000000-0000-0000-0000-0000000000000" - + + [] let ``Submit extracts the location from a quantum endpoint`` () = let given = test "Returns Unit" diff --git a/src/Simulation/EntryPointDriver/Azure/AzureSettings.cs b/src/Simulation/EntryPointDriver/Azure/AzureSettings.cs index 87dedafc164..3577d5258f6 100644 --- a/src/Simulation/EntryPointDriver/Azure/AzureSettings.cs +++ b/src/Simulation/EntryPointDriver/Azure/AzureSettings.cs @@ -136,19 +136,20 @@ internal TokenCredential CreateCredentials() } } - internal string? TrimmedUserAgent() { - var userAgent = (UserAgent ?? System.Environment.GetEnvironmentVariable("USER_AGENT"))?.Trim(); - - return (userAgent == null || userAgent.Length < 25) - ? userAgent - : userAgent.Substring(0, 24); - } - - internal QuantumJobClientOptions CreateClientOptions() { var options = new QuantumJobClientOptions(); - options.Diagnostics.ApplicationId = TrimmedUserAgent(); + + // This value will be added as a prefix in the UserAgent when + // calling the Azure Quantum API + // It cannot be larger than 24 characters. + var applicationId = string.Join('@', "Q#Run", UserAgent?.Trim()).Trim(' ', '@'); + if (applicationId?.Length > 24) + { + applicationId = applicationId.Substring(0, 24); + } + + options.Diagnostics.ApplicationId = applicationId; return options; } @@ -187,7 +188,7 @@ public override string ToString() => string.Join( $"Location: {Location ?? ExtractLocation(BaseUri)}", $"Credential: {Credential}", $"AadToken: {AadToken?.Substring(0, 5)}", - $"UserAgent: {TrimmedUserAgent()}", + $"UserAgent: {UserAgent}", $"Job Name: {JobName}", $"Job Parameters: {string.Join(", ", JobParams.OrderBy(item => item.Key))}", $"Shots: {Shots}",