diff --git a/src/AzureClient/AzureClient.cs b/src/AzureClient/AzureClient.cs
index 3d941f5233..371ab20c95 100644
--- a/src/AzureClient/AzureClient.cs
+++ b/src/AzureClient/AzureClient.cs
@@ -1,24 +1,222 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#nullable enable
using System;
using System.Collections.Generic;
-using System.Text;
-using Microsoft.Jupyter.Core;
+using System.Linq;
+using System.IO;
using System.Threading.Tasks;
-
+using Microsoft.Azure.Quantum.Client;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
-using System.Linq;
-using System.IO;
-using Microsoft.Quantum.Runtime;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.Simulation.Core;
namespace Microsoft.Quantum.IQSharp.AzureClient
{
///
public class AzureClient : IAzureClient
{
+ private string ConnectionString { get; set; } = string.Empty;
+ private string ActiveTargetName { get; set; } = string.Empty;
+ private AuthenticationResult? AuthenticationResult { get; set; }
+ private IQuantumClient? QuantumClient { get; set; }
+ private Azure.Quantum.Workspace? ActiveWorkspace { get; set; }
+
+ ///
+ public async Task ConnectAsync(
+ IChannel channel,
+ string subscriptionId,
+ string resourceGroupName,
+ string workspaceName,
+ string storageAccountConnectionString,
+ bool forceLoginPrompt = false)
+ {
+ ConnectionString = storageAccountConnectionString;
+
+ var clientId = "84ba0947-6c53-4dd2-9ca9-b3694761521b"; // Microsoft Quantum Development Kit
+ var authority = "https://login.microsoftonline.com/common";
+ var msalApp = PublicClientApplicationBuilder.Create(clientId).WithAuthority(authority).Build();
+
+ // Register the token cache for serialization
+ var cacheFileName = "aad.bin";
+ var cacheDirectoryEnvVarName = "AZURE_QUANTUM_TOKEN_CACHE";
+ var cacheDirectory = System.Environment.GetEnvironmentVariable(cacheDirectoryEnvVarName);
+ if (string.IsNullOrEmpty(cacheDirectory))
+ {
+ cacheDirectory = Path.Join(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), ".azure-quantum");
+ }
+
+ var storageCreationProperties = new StorageCreationPropertiesBuilder(cacheFileName, cacheDirectory, clientId).Build();
+ var cacheHelper = await MsalCacheHelper.CreateAsync(storageCreationProperties);
+ cacheHelper.RegisterCache(msalApp.UserTokenCache);
+
+ var scopes = new List() { "https://quantum.microsoft.com/Jobs.ReadWrite" };
+
+ bool shouldShowLoginPrompt = forceLoginPrompt;
+ if (!shouldShowLoginPrompt)
+ {
+ try
+ {
+ var accounts = await msalApp.GetAccountsAsync();
+ AuthenticationResult = await msalApp.AcquireTokenSilent(
+ scopes, accounts.FirstOrDefault()).WithAuthority(msalApp.Authority).ExecuteAsync();
+ }
+ catch (MsalUiRequiredException)
+ {
+ shouldShowLoginPrompt = true;
+ }
+ }
+
+ if (shouldShowLoginPrompt)
+ {
+ AuthenticationResult = await msalApp.AcquireTokenWithDeviceCode(scopes,
+ deviceCodeResult =>
+ {
+ channel.Stdout(deviceCodeResult.Message);
+ return Task.FromResult(0);
+ }).WithAuthority(msalApp.Authority).ExecuteAsync();
+ }
+
+ if (AuthenticationResult == null)
+ {
+ return AzureClientError.AuthenticationFailed.ToExecutionResult();
+ }
+
+ var credentials = new Rest.TokenCredentials(AuthenticationResult.AccessToken);
+ QuantumClient = new QuantumClient(credentials)
+ {
+ SubscriptionId = subscriptionId,
+ ResourceGroupName = resourceGroupName,
+ WorkspaceName = workspaceName
+ };
+ ActiveWorkspace = new Azure.Quantum.Workspace(
+ QuantumClient.SubscriptionId, QuantumClient.ResourceGroupName,
+ QuantumClient.WorkspaceName, AuthenticationResult?.AccessToken);
+
+ try
+ {
+ var jobsList = await QuantumClient.Jobs.ListAsync();
+ channel.Stdout($"Successfully connected to Azure Quantum workspace {workspaceName}.");
+ }
+ catch (Exception e)
+ {
+ channel.Stderr(e.ToString());
+ return AzureClientError.WorkspaceNotFound.ToExecutionResult();
+ }
+
+ return QuantumClient.ToJupyterTable().ToExecutionResult();
+ }
+
+ ///
+ public async Task PrintConnectionStatusAsync(IChannel channel) =>
+ QuantumClient == null
+ ? AzureClientError.NotConnected.ToExecutionResult()
+ : QuantumClient.ToJupyterTable().ToExecutionResult();
+
+ ///
+ public async Task SubmitJobAsync(
+ IChannel channel,
+ IOperationResolver operationResolver,
+ string operationName)
+ {
+ if (ActiveWorkspace == null)
+ {
+ channel.Stderr("Please call %connect before submitting a job.");
+ return AzureClientError.NotConnected.ToExecutionResult();
+ }
+
+ if (ActiveTargetName == null)
+ {
+ channel.Stderr("Please call %target before submitting a job.");
+ return AzureClientError.NoTarget.ToExecutionResult();
+ }
+
+ if (string.IsNullOrEmpty(operationName))
+ {
+ channel.Stderr("Please pass a valid Q# operation name to %submit.");
+ return AzureClientError.NoOperationName.ToExecutionResult();
+ }
+
+ var operationInfo = operationResolver.Resolve(operationName);
+ var entryPointInfo = new EntryPointInfo(operationInfo.RoslynType);
+ var entryPointInput = QVoid.Instance;
+ var machine = Azure.Quantum.QuantumMachineFactory.CreateMachine(ActiveWorkspace, ActiveTargetName, ConnectionString);
+ if (machine == null)
+ {
+ channel.Stderr($"Could not find an execution target for target {ActiveTargetName}.");
+ return AzureClientError.NoTarget.ToExecutionResult();
+ }
+
+ var job = await machine.SubmitAsync(entryPointInfo, entryPointInput);
+ return job.ToJupyterTable().ToExecutionResult();
+ }
+
+ ///
+ public async Task SetActiveTargetAsync(
+ IChannel channel,
+ string targetName)
+ {
+ // TODO: Validate that this target name is valid in the workspace.
+ ActiveTargetName = targetName;
+ return $"Active target is now {ActiveTargetName}".ToExecutionResult();
+ }
+
+ ///
+ public async Task PrintTargetListAsync(
+ IChannel channel)
+ {
+ if (QuantumClient == null)
+ {
+ channel.Stderr("Please call %connect before listing targets.");
+ return AzureClientError.NotConnected.ToExecutionResult();
+ }
+
+ var providersStatus = await QuantumClient.Providers.GetStatusAsync();
+ return providersStatus.ToJupyterTable().ToExecutionResult();
+ }
+
+ ///
+ public async Task PrintJobStatusAsync(
+ IChannel channel,
+ string jobId)
+ {
+ if (QuantumClient == null)
+ {
+ channel.Stderr("Please call %connect before getting job status.");
+ return AzureClientError.NotConnected.ToExecutionResult();
+ }
+
+ var jobDetails = await QuantumClient.Jobs.GetAsync(jobId);
+ if (jobDetails == null)
+ {
+ channel.Stderr($"Job ID {jobId} not found in current Azure Quantum workspace.");
+ return AzureClientError.JobNotFound.ToExecutionResult();
+ }
+
+ return jobDetails.ToJupyterTable().ToExecutionResult();
+ }
+
+ ///
+ public async Task PrintJobListAsync(
+ IChannel channel)
+ {
+ if (QuantumClient == null)
+ {
+ channel.Stderr("Please call %connect before listing jobs.");
+ return AzureClientError.NotConnected.ToExecutionResult();
+ }
+
+ var jobsList = await QuantumClient.Jobs.ListAsync();
+ if (jobsList == null || jobsList.Count() == 0)
+ {
+ channel.Stderr("No jobs found in current Azure Quantum workspace.");
+ return AzureClientError.JobNotFound.ToExecutionResult();
+ }
+
+ return jobsList.ToJupyterTable().ToExecutionResult();
+ }
}
}
diff --git a/src/AzureClient/AzureClient.csproj b/src/AzureClient/AzureClient.csproj
index e4da5c48b2..3b9d29e070 100644
--- a/src/AzureClient/AzureClient.csproj
+++ b/src/AzureClient/AzureClient.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
@@ -13,8 +13,9 @@
-
-
+
+
+
diff --git a/src/AzureClient/Extensions.cs b/src/AzureClient/Extensions.cs
index fbfe0a4a8e..7a8e950951 100644
--- a/src/AzureClient/Extensions.cs
+++ b/src/AzureClient/Extensions.cs
@@ -1,12 +1,18 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#nullable enable
using System;
using System.Collections.Generic;
-using System.Text;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Azure.Quantum.Client;
+using Microsoft.Azure.Quantum.Client.Models;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.Runtime;
namespace Microsoft.Quantum.IQSharp.AzureClient
{
@@ -22,5 +28,96 @@ public static void AddAzureClient(this IServiceCollection services)
{
services.AddSingleton();
}
+
+ ///
+ /// Encapsulates a given as the result of an execution.
+ ///
+ ///
+ /// The result of an IAzureClient API call.
+ ///
+ public static ExecutionResult ToExecutionResult(this AzureClientError azureClientError) =>
+ new ExecutionResult
+ {
+ Status = ExecuteStatus.Error,
+ Output = azureClientError.ToDescription()
+ };
+
+ ///
+ /// Returns the string value of the for the given
+ /// enumeration value.
+ ///
+ ///
+ ///
+ public static string ToDescription(this AzureClientError azureClientError)
+ {
+ var attributes = azureClientError
+ .GetType()
+ .GetField(azureClientError.ToString())
+ .GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
+ return attributes?.Length > 0 ? attributes[0].Description : string.Empty;
+ }
+
+ ///
+ /// Encapsulates a given as the result of an execution.
+ ///
+ ///
+ /// A task which will return the result of an IAzureClient API call.
+ ///
+ public static async Task ToExecutionResult(this Task task) =>
+ (await task).ToExecutionResult();
+
+ internal static Table ToJupyterTable(this JobDetails jobDetails) =>
+ new List { jobDetails }.ToJupyterTable();
+
+ internal static Table ToJupyterTable(this IEnumerable jobsList) =>
+ new Table
+ {
+ Columns = new List<(string, Func)>
+ {
+ ("JobId", jobDetails => jobDetails.Id),
+ ("JobName", jobDetails => jobDetails.Name),
+ ("JobStatus", jobDetails => jobDetails.Status),
+ ("Provider", jobDetails => jobDetails.ProviderId),
+ ("Target", jobDetails => jobDetails.Target),
+ },
+ Rows = jobsList.ToList()
+ };
+
+ internal static Table ToJupyterTable(this IQuantumMachineJob job) =>
+ new Table
+ {
+ Columns = new List<(string, Func)>
+ {
+ ("JobId", job => job.Id),
+ ("JobStatus", job => job.Status),
+ ("JobUri", job => job.Uri.ToString()),
+ },
+ Rows = new List() { job }
+ };
+
+ internal static Table ToJupyterTable(this IQuantumClient quantumClient) =>
+ new Table
+ {
+ Columns = new List<(string, Func)>
+ {
+ ("SubscriptionId", quantumClient => quantumClient.SubscriptionId),
+ ("ResourceGroupName", quantumClient => quantumClient.ResourceGroupName),
+ ("WorkspaceName", quantumClient => quantumClient.WorkspaceName),
+ },
+ Rows = new List() { quantumClient }
+ };
+
+ internal static Table ToJupyterTable(this IEnumerable providerStatusList) =>
+ new Table
+ {
+ Columns = new List<(string, Func)>
+ {
+ ("TargetId", target => target.Id),
+ ("CurrentAvailability", target => target.CurrentAvailability),
+ ("AverageQueueTime", target => target.AverageQueueTime.ToString()),
+ ("StatusPage", target => target.StatusPage),
+ },
+ Rows = providerStatusList.SelectMany(provider => provider.Targets).ToList()
+ };
}
}
diff --git a/src/AzureClient/IAzureClient.cs b/src/AzureClient/IAzureClient.cs
index 8efd69e5a7..d98c420431 100644
--- a/src/AzureClient/IAzureClient.cs
+++ b/src/AzureClient/IAzureClient.cs
@@ -1,21 +1,117 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#nullable enable
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Microsoft.Jupyter.Core;
+using System.ComponentModel;
using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
namespace Microsoft.Quantum.IQSharp.AzureClient
{
+ ///
+ /// Describes possible error results from methods.
+ ///
+ public enum AzureClientError
+ {
+ ///
+ /// Method completed with an unknown error.
+ ///
+ [Description(Resources.AzureClientErrorUnknownError)]
+ UnknownError = 0,
+
+ ///
+ /// No connection has been made to any Azure Quantum workspace.
+ ///
+ [Description(Resources.AzureClientErrorNotConnected)]
+ NotConnected = 1,
+
+ ///
+ /// A target has not yet been configured for job submission.
+ ///
+ [Description(Resources.AzureClientErrorNoTarget)]
+ NoTarget = 2,
+
+ ///
+ /// A job meeting the specified criteria was not found.
+ ///
+ [Description(Resources.AzureClientErrorJobNotFound)]
+ JobNotFound = 3,
+
+ ///
+ /// No Q# operation name was provided where one was required.
+ ///
+ [Description(Resources.AzureClientErrorNoOperationName)]
+ NoOperationName = 4,
+
+ ///
+ /// Authentication with the Azure service failed.
+ ///
+ [Description(Resources.AzureClientErrorAuthenticationFailed)]
+ AuthenticationFailed = 5,
+
+ ///
+ /// A workspace meeting the specified criteria was not found.
+ ///
+ [Description(Resources.AzureClientErrorWorkspaceNotFound)]
+ WorkspaceNotFound = 6,
+ }
+
///
/// This service is capable of connecting to Azure Quantum workspaces
/// and submitting jobs.
///
public interface IAzureClient
{
+ ///
+ /// Connects to the specified Azure Quantum workspace, first logging into Azure if necessary.
+ ///
+ public Task ConnectAsync(
+ IChannel channel,
+ string subscriptionId,
+ string resourceGroupName,
+ string workspaceName,
+ string storageAccountConnectionString,
+ bool forceLogin = false);
+
+ ///
+ /// Prints a string describing the current connection status.
+ ///
+ public Task PrintConnectionStatusAsync(
+ IChannel channel);
+
+ ///
+ /// Submits the specified Q# operation as a job to the currently active target.
+ ///
+ public Task SubmitJobAsync(
+ IChannel channel,
+ IOperationResolver operationResolver,
+ string operationName);
+
+ ///
+ /// Sets the specified target for job submission.
+ ///
+ public Task SetActiveTargetAsync(
+ IChannel channel,
+ string targetName);
+
+ ///
+ /// Prints the list of targets currently provisioned in the current workspace.
+ ///
+ public Task PrintTargetListAsync(
+ IChannel channel);
+
+ ///
+ /// Prints the job status corresponding to the given job ID.
+ ///
+ public Task PrintJobStatusAsync(
+ IChannel channel,
+ string jobId);
+
+ ///
+ /// Prints a list of all jobs in the current workspace.
+ ///
+ public Task PrintJobListAsync(
+ IChannel channel);
}
}
diff --git a/src/AzureClient/Magic/AzureClientMagicBase.cs b/src/AzureClient/Magic/AzureClientMagicBase.cs
new file mode 100644
index 0000000000..f7eac3b5ce
--- /dev/null
+++ b/src/AzureClient/Magic/AzureClientMagicBase.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp.Jupyter;
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// Base class used for Azure Client magic commands.
+ ///
+ public abstract class AzureClientMagicBase : AbstractMagic
+ {
+ ///
+ /// The object used by this magic command to interact with Azure.
+ ///
+ public IAzureClient AzureClient { get; }
+
+ ///
+ /// Constructs the Azure Client magic command with the specified keyword
+ /// and documentation.
+ ///
+ /// The object used to interact with Azure.
+ /// The name used to invoke the magic command.
+ /// Documentation describing the usage of this magic command.
+ public AzureClientMagicBase(IAzureClient azureClient, string keyword, Documentation docs):
+ base(keyword, docs)
+ {
+ this.AzureClient = azureClient;
+ }
+
+ ///
+ public override ExecutionResult Run(string input, IChannel channel) =>
+ RunAsync(input, channel).GetAwaiter().GetResult();
+
+ ///
+ /// Executes the magic command functionality for the given input.
+ ///
+ public abstract Task RunAsync(string input, IChannel channel);
+ }
+}
diff --git a/src/AzureClient/Magic/ConnectMagic.cs b/src/AzureClient/Magic/ConnectMagic.cs
new file mode 100644
index 0000000000..4a325a923b
--- /dev/null
+++ b/src/AzureClient/Magic/ConnectMagic.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp.Jupyter;
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// A magic command that can be used to connect to an Azure workspace.
+ ///
+ public class ConnectMagic : AzureClientMagicBase
+ {
+ private const string
+ ParameterNameLogin = "login",
+ ParameterNameStorageAccountConnectionString = "storageAccountConnectionString",
+ ParameterNameSubscriptionId = "subscriptionId",
+ ParameterNameResourceGroupName = "resourceGroupName",
+ ParameterNameWorkspaceName = "workspaceName";
+
+ ///
+ /// Constructs a new magic command given an IAzureClient object.
+ ///
+ public ConnectMagic(IAzureClient azureClient) :
+ base(azureClient,
+ "connect",
+ new Documentation
+ {
+ Summary = "Connects to an Azure workspace or displays current connection status.",
+ Description = @"
+ This magic command allows for connecting to an Azure Quantum workspace
+ as specified by a valid subscription ID, resource group name, workspace name,
+ and storage account connection string.
+ ".Dedent(),
+ Examples = new[]
+ {
+ @"
+ Print information about the current connection:
+ ```
+ In []: %connect
+ Out[]: Connected to WORKSPACE_NAME
+ ```
+ ".Dedent(),
+
+ $@"
+ Connect to an Azure Quantum workspace:
+ ```
+ In []: %connect {ParameterNameSubscriptionId}=SUBSCRIPTION_ID
+ {ParameterNameResourceGroupName}=RESOURCE_GROUP_NAME
+ {ParameterNameWorkspaceName}=WORKSPACE_NAME
+ {ParameterNameStorageAccountConnectionString}=CONNECTION_STRING
+ Out[]: Connected to WORKSPACE_NAME
+ ```
+ ".Dedent(),
+
+ $@"
+ Connect to an Azure Quantum workspace and force a credential prompt:
+ ```
+ In []: %connect {ParameterNameLogin}
+ {ParameterNameSubscriptionId}=SUBSCRIPTION_ID
+ {ParameterNameResourceGroupName}=RESOURCE_GROUP_NAME
+ {ParameterNameWorkspaceName}=WORKSPACE_NAME
+ {ParameterNameStorageAccountConnectionString}=CONNECTION_STRING
+ Out[]: To sign in, use a web browser to open the page https://microsoft.com/devicelogin
+ and enter the code [login code] to authenticate.
+ Connected to WORKSPACE_NAME
+ ```
+ Use the `{ParameterNameLogin}` option if you want to bypass any saved or cached
+ credentials when connecting to Azure.
+ ".Dedent()
+ }
+ }) {}
+
+ ///
+ /// Connects to an Azure workspace given a subscription ID, resource group name,
+ /// workspace name, and connection string as a JSON-encoded object.
+ ///
+ public override async Task RunAsync(string input, IChannel channel)
+ {
+ var inputParameters = ParseInputParameters(input);
+
+ var storageAccountConnectionString = inputParameters.DecodeParameter(ParameterNameStorageAccountConnectionString);
+ if (string.IsNullOrEmpty(storageAccountConnectionString))
+ {
+ return await AzureClient.PrintConnectionStatusAsync(channel);
+ }
+
+ var subscriptionId = inputParameters.DecodeParameter(ParameterNameSubscriptionId);
+ var resourceGroupName = inputParameters.DecodeParameter(ParameterNameResourceGroupName);
+ var workspaceName = inputParameters.DecodeParameter(ParameterNameWorkspaceName);
+ var forceLogin = inputParameters.DecodeParameter(ParameterNameLogin, defaultValue: false);
+ return await AzureClient.ConnectAsync(
+ channel,
+ subscriptionId,
+ resourceGroupName,
+ workspaceName,
+ storageAccountConnectionString,
+ forceLogin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AzureClient/Magic/StatusMagic.cs b/src/AzureClient/Magic/StatusMagic.cs
new file mode 100644
index 0000000000..d39c6927da
--- /dev/null
+++ b/src/AzureClient/Magic/StatusMagic.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp.Jupyter;
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// A magic command that can be used to connect to an Azure workspace.
+ ///
+ public class StatusMagic : AzureClientMagicBase
+ {
+ private const string
+ ParameterNameJobId = "jobId";
+
+ ///
+ /// Constructs a new magic command given an IAzureClient object.
+ ///
+ public StatusMagic(IAzureClient azureClient) :
+ base(azureClient,
+ "status",
+ new Documentation
+ {
+ Summary = "Displays status for jobs in the current Azure Quantum workspace.",
+ Description = @"
+ This magic command allows for displaying status of jobs in the current
+ Azure Quantum workspace. If a valid job ID is provided as an argument, the
+ detailed status of that job will be displayed; otherwise, a list of all jobs
+ created in the current session will be displayed.
+ ".Dedent(),
+ Examples = new[]
+ {
+ @"
+ Print status about a specific job:
+ ```
+ In []: %status JOB_ID
+ Out[]: JOB_ID:
+ ```
+ ".Dedent(),
+
+ @"
+ Print status about all jobs created in the current session:
+ ```
+ In []: %status
+ Out[]:
+ ```
+ ".Dedent()
+ }
+ }) {}
+
+ ///
+ /// Displays the status corresponding to a given job ID, if provided,
+ /// or all jobs in the active workspace.
+ ///
+ public override async Task RunAsync(string input, IChannel channel)
+ {
+ var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameJobId);
+ if (inputParameters.ContainsKey(ParameterNameJobId))
+ {
+ string jobId = inputParameters.DecodeParameter(ParameterNameJobId);
+ return await AzureClient.PrintJobStatusAsync(channel, jobId);
+ }
+
+ return await AzureClient.PrintJobListAsync(channel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AzureClient/Magic/SubmitMagic.cs b/src/AzureClient/Magic/SubmitMagic.cs
new file mode 100644
index 0000000000..d89da233b3
--- /dev/null
+++ b/src/AzureClient/Magic/SubmitMagic.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// A magic command that can be used to submit jobs to an Azure Quantum workspace.
+ ///
+ public class SubmitMagic : AzureClientMagicBase
+ {
+ ///
+ /// The symbol resolver used by this magic command to find
+ /// operations or functions to be simulated.
+ ///
+ public IOperationResolver OperationResolver { get; }
+
+ ///
+ /// Constructs a new magic command given a resolver used to find
+ /// operations and functions and an IAzureClient object.
+ ///
+ public SubmitMagic(IOperationResolver operationResolver, IAzureClient azureClient) :
+ base(azureClient,
+ "submit",
+ new Documentation
+ {
+ Summary = "Submits a job to an Azure Quantum workspace.",
+ Description = @"
+ This magic command allows for submitting a job to an Azure Quantum workspace
+ corresponding to the Q# operation provided as an argument.
+
+ The Azure Quantum workspace must previously have been initialized
+ using the %connect magic command.
+ ".Dedent(),
+ Examples = new[]
+ {
+ @"
+ Submit an operation as a new job to the current Azure Quantum workspace:
+ ```
+ In []: %submit OPERATION_NAME
+ Out[]: Submitted job JOB_ID
+ ```
+ ".Dedent(),
+ }
+ }) =>
+ this.OperationResolver = operationResolver;
+
+ ///
+ /// Submits a new job to an Azure Quantum workspace given a Q# operation
+ /// name that is present in the current Q# Jupyter workspace.
+ ///
+ public override async Task RunAsync(string input, IChannel channel)
+ {
+ Dictionary keyValuePairs = ParseInputParameters(input);
+ var operationName = keyValuePairs.Keys.FirstOrDefault();
+ return await AzureClient.SubmitJobAsync(channel, OperationResolver, operationName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AzureClient/Magic/TargetMagic.cs b/src/AzureClient/Magic/TargetMagic.cs
new file mode 100644
index 0000000000..4108009cc6
--- /dev/null
+++ b/src/AzureClient/Magic/TargetMagic.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp.Jupyter;
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// A magic command that can be used to view or set target information for an Azure Quantum workspace.
+ ///
+ public class TargetMagic : AzureClientMagicBase
+ {
+ private const string
+ ParameterNameTargetName = "name";
+
+ ///
+ /// Constructs a new magic command given an IAzureClient object.
+ ///
+ public TargetMagic(IAzureClient azureClient) :
+ base(azureClient,
+ "target",
+ new Documentation
+ {
+ Summary = "Views or sets the target for job submission to an Azure Quantum workspace.",
+ Description = @"
+ This magic command allows for specifying a target for job submission
+ to an Azure Quantum workspace, or viewing the list of all available targets.
+
+ The Azure Quantum workspace must previously have been initialized
+ using the %connect magic command, and the specified target must be
+ available in the workspace.
+ ".Dedent(),
+ Examples = new[]
+ {
+ @"
+ Set the current target for job submission:
+ ```
+ In []: %target TARGET_NAME
+ Out[]: Active target is now TARGET_NAME
+ ```
+ ".Dedent(),
+ @"
+ View the current target and all available targets in the current Azure Quantum workspace:
+ ```
+ In []: %target
+ Out[]:
+ ```
+ ".Dedent(),
+ }
+ })
+ {
+ }
+
+ ///
+ /// Sets or views the target for job submission to the current Azure Quantum workspace.
+ ///
+ public override async Task RunAsync(string input, IChannel channel)
+ {
+ var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameTargetName);
+ if (inputParameters.ContainsKey(ParameterNameTargetName))
+ {
+ string targetName = inputParameters.DecodeParameter(ParameterNameTargetName);
+ return await AzureClient.SetActiveTargetAsync(channel, targetName);
+ }
+
+ return await AzureClient.PrintTargetListAsync(channel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AzureClient/Resources.cs b/src/AzureClient/Resources.cs
new file mode 100644
index 0000000000..6c3de47cc0
--- /dev/null
+++ b/src/AzureClient/Resources.cs
@@ -0,0 +1,31 @@
+#nullable enable
+
+namespace Microsoft.Quantum.IQSharp.AzureClient
+{
+ ///
+ /// This class contains resources that will eventually be exposed to localization.
+ ///
+ internal static class Resources
+ {
+ public const string AzureClientErrorUnknownError =
+ "An unknown error occurred.";
+
+ public const string AzureClientErrorNotConnected =
+ "Not connected to any Azure Quantum workspace.";
+
+ public const string AzureClientErrorNoTarget =
+ "No execution target has been configured for Azure Quantum job submission.";
+
+ public const string AzureClientErrorJobNotFound =
+ "No job with the given ID was found in the current Azure Quantum workspace.";
+
+ public const string AzureClientErrorNoOperationName =
+ "No Q# operation name was specified for Azure Quantum job submission.";
+
+ public const string AzureClientErrorAuthenticationFailed =
+ "Failed to authenticate to the specified Azure Quantum workspace.";
+
+ public const string AzureClientErrorWorkspaceNotFound =
+ "No Azure Quantum workspace was found that matches the specified criteria.";
+ }
+}
diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 249d061dcd..b67e57c334 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -34,9 +34,9 @@
-
-
-
+
+
+
diff --git a/src/Jupyter/Extensions.cs b/src/Jupyter/Extensions.cs
index 5a602d89d4..b016d9c328 100644
--- a/src/Jupyter/Extensions.cs
+++ b/src/Jupyter/Extensions.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -9,6 +11,7 @@
using Microsoft.Quantum.Simulation.Common;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;
+using Newtonsoft.Json;
namespace Microsoft.Quantum.IQSharp.Jupyter
{
@@ -174,5 +177,17 @@ public static string Dedent(this string text)
var leftTrimRegex = new Regex(@$"^[ \t]{{{minWhitespace}}}", RegexOptions.Multiline);
return leftTrimRegex.Replace(text, "");
}
+
+ ///
+ /// Retrieves and JSON-decodes the value for the given parameter name.
+ ///
+ public static T DecodeParameter(this Dictionary parameters, string parameterName, T defaultValue = default)
+ {
+ if (!parameters.TryGetValue(parameterName, out string parameterValue))
+ {
+ return defaultValue;
+ }
+ return (T)(JsonConvert.DeserializeObject(parameterValue)) ?? defaultValue;
+ }
}
}
diff --git a/src/Kernel/Magic/AbstractMagic.cs b/src/Jupyter/Magic/AbstractMagic.cs
similarity index 51%
rename from src/Kernel/Magic/AbstractMagic.cs
rename to src/Jupyter/Magic/AbstractMagic.cs
index 9babaf2ca1..1b0cd24c3a 100644
--- a/src/Kernel/Magic/AbstractMagic.cs
+++ b/src/Jupyter/Magic/AbstractMagic.cs
@@ -3,12 +3,15 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp.Common;
-using Microsoft.Quantum.IQSharp.Kernel;
+using Microsoft.Quantum.QsCompiler.Serialization;
+using Newtonsoft.Json.Linq;
-namespace Microsoft.Quantum.IQSharp.Kernel
+namespace Microsoft.Quantum.IQSharp.Jupyter
{
///
/// Abstract base class for IQ# magic symbols.
@@ -78,6 +81,67 @@ public static (string, Dictionary) ParseInput(string input)
return (name, args);
}
+ ///
+ /// Parses the input to a magic command, interpreting the input as
+ /// a name followed by a JSON-serialized dictionary.
+ ///
+ public static Dictionary JsonToDict(string input) =>
+ !string.IsNullOrEmpty(input) ? JsonConverters.JsonToDict(input) : new Dictionary { };
+
+ ///
+ /// Parses the input parameters for a given magic symbol and returns a
+ /// Dictionary with the names and values of the parameters,
+ /// where the values of the Dictionary are JSON-serialized objects.
+ ///
+ public Dictionary ParseInputParameters(string input, string firstParameterInferredName = "")
+ {
+ Dictionary inputParameters = new Dictionary();
+
+ var args = input.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
+
+ // If we are expecting a first inferred-name parameter, see if it exists.
+ // If so, serialize it to the dictionary as JSON and remove it from the list of args.
+ if (args.Length > 0 &&
+ !args[0].StartsWith("{") &&
+ !args[0].Contains("=") &&
+ !string.IsNullOrEmpty(firstParameterInferredName))
+ {
+ using (var writer = new StringWriter())
+ {
+ Json.Serializer.Serialize(writer, args[0]);
+ inputParameters[firstParameterInferredName] = writer.ToString();
+ }
+ args = args.Where((_, index) => index != 0).ToArray();
+ }
+
+ // See if the remaining arguments look like JSON. If so, try to parse as JSON.
+ // Otherwise, try to parse as key=value pairs and serialize into the dictionary as JSON.
+ if (args.Length > 0 && args[0].StartsWith("{"))
+ {
+ var jsonArgs = JsonToDict(string.Join(" ", args));
+ foreach (var (key, jsonValue) in jsonArgs)
+ {
+ inputParameters[key] = jsonValue;
+ }
+ }
+ else
+ {
+ foreach (string arg in args)
+ {
+ var tokens = arg.Split("=", 2);
+ var key = tokens[0].Trim();
+ var value = (tokens.Length == 1) ? true as object : tokens[1].Trim() as object;
+ using (var writer = new StringWriter())
+ {
+ Json.Serializer.Serialize(writer, value);
+ inputParameters[key] = writer.ToString();
+ }
+ }
+ }
+
+ return inputParameters;
+ }
+
///
/// A method to be run when the magic command is executed.
///
diff --git a/src/Kernel/Magic/LsMagicMagic.cs b/src/Kernel/Magic/LsMagicMagic.cs
index 891d2f92b6..05e1037e00 100644
--- a/src/Kernel/Magic/LsMagicMagic.cs
+++ b/src/Kernel/Magic/LsMagicMagic.cs
@@ -5,7 +5,7 @@
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp;
-using Microsoft.Quantum.IQSharp.Kernel;
+using Microsoft.Quantum.IQSharp.Jupyter;
namespace Microsoft.Quantum.IQSharp.Kernel
{
diff --git a/src/Kernel/Magic/Simulate.cs b/src/Kernel/Magic/Simulate.cs
index d3cea8b17a..2b94eb9b06 100644
--- a/src/Kernel/Magic/Simulate.cs
+++ b/src/Kernel/Magic/Simulate.cs
@@ -18,6 +18,9 @@ namespace Microsoft.Quantum.IQSharp.Kernel
///
public class SimulateMagic : AbstractMagic
{
+ private const string
+ ParameterNameOperationName = "operationName";
+
///
/// Constructs a new magic command given a resolver used to find
/// operations and functions, and a configuration source used to set
@@ -55,15 +58,16 @@ public override ExecutionResult Run(string input, IChannel channel) =>
///
public async Task RunAsync(string input, IChannel channel)
{
- var (name, args) = ParseInput(input);
+ var inputParameters = ParseInputParameters(input, firstParameterInferredName: ParameterNameOperationName);
+ var name = inputParameters.DecodeParameter(ParameterNameOperationName);
var symbol = SymbolResolver.Resolve(name) as IQSharpSymbol;
if (symbol == null) throw new InvalidOperationException($"Invalid operation name: {name}");
using var qsim = new QuantumSimulator()
.WithJupyterDisplay(channel, ConfigurationSource)
.WithStackTraceDisplay(channel);
- var value = await symbol.Operation.RunAsync(qsim, args);
+ var value = await symbol.Operation.RunAsync(qsim, inputParameters);
return value.ToExecutionResult();
}
}
diff --git a/src/Kernel/Magic/WhoMagic.cs b/src/Kernel/Magic/WhoMagic.cs
index 7efadeddb5..9a8c514f4d 100644
--- a/src/Kernel/Magic/WhoMagic.cs
+++ b/src/Kernel/Magic/WhoMagic.cs
@@ -5,7 +5,7 @@
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp;
-using Microsoft.Quantum.IQSharp.Kernel;
+using Microsoft.Quantum.IQSharp.Jupyter;
namespace Microsoft.Quantum.IQSharp.Kernel
{
diff --git a/src/Kernel/Magic/WorkspaceMagic.cs b/src/Kernel/Magic/WorkspaceMagic.cs
index 0f347295d3..7ef9ee2996 100644
--- a/src/Kernel/Magic/WorkspaceMagic.cs
+++ b/src/Kernel/Magic/WorkspaceMagic.cs
@@ -5,7 +5,7 @@
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp.Common;
-using Microsoft.Quantum.IQSharp.Kernel;
+using Microsoft.Quantum.IQSharp.Jupyter;
namespace Microsoft.Quantum.IQSharp.Kernel
{
diff --git a/src/Python/qsharp/azure.py b/src/Python/qsharp/azure.py
new file mode 100644
index 0000000000..2182a20b30
--- /dev/null
+++ b/src/Python/qsharp/azure.py
@@ -0,0 +1,47 @@
+#!/bin/env python
+# -*- coding: utf-8 -*-
+##
+# azure.py: enables using Q# quantum execution on Azure Quantum from Python.
+##
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+##
+
+## IMPORTS ##
+
+import qsharp
+import json
+import typing
+from typing import List, Dict, Callable, Any
+
+from qsharp.serialization import map_tuples
+from typing import List, Tuple, Dict, Iterable
+from enum import Enum
+
+## LOGGING ##
+
+import logging
+logger = logging.getLogger(__name__)
+
+## EXPORTS ##
+
+__all__ = [
+ 'connect',
+ 'target',
+ 'submit',
+ 'status'
+]
+
+## FUNCTIONS ##
+
+def connect(**params) -> Any:
+ return qsharp.client._execute_magic(f"connect", raise_on_stderr=False, **params)
+
+def target(name : str = '', **params) -> Any:
+ return qsharp.client._execute_magic(f"target {name}", raise_on_stderr=False, **params)
+
+def submit(op, **params) -> Any:
+ return qsharp.client._execute_callable_magic("submit", op, raise_on_stderr=False, **params)
+
+def status(jobId : str = '', **params) -> Any:
+ return qsharp.client._execute_magic(f"status {jobId}", raise_on_stderr=False, **params)
diff --git a/src/Tests/AzureClientMagicTests.cs b/src/Tests/AzureClientMagicTests.cs
new file mode 100644
index 0000000000..730554ede8
--- /dev/null
+++ b/src/Tests/AzureClientMagicTests.cs
@@ -0,0 +1,181 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp;
+using Microsoft.Quantum.IQSharp.AzureClient;
+
+namespace Tests.IQSharp
+{
+ public static class AzureClientMagicTestExtensions
+ {
+ public static void Test(this MagicSymbol magic, string input, ExecuteStatus expected = ExecuteStatus.Ok)
+ {
+ var result = magic.Execute(input, new MockChannel()).GetAwaiter().GetResult();
+ Assert.IsTrue(result.Status == expected);
+ }
+ }
+
+ [TestClass]
+ public class AzureClientMagicTests
+ {
+ private readonly string subscriptionId = "TEST_SUBSCRIPTION_ID";
+ private readonly string resourceGroupName = "TEST_RESOURCE_GROUP_NAME";
+ private readonly string workspaceName = "TEST_WORKSPACE_NAME";
+ private readonly string storageAccountConnectionString = "TEST_CONNECTION_STRING";
+ private readonly string jobId = "TEST_JOB_ID";
+ private readonly string operationName = "TEST_OPERATION_NAME";
+ private readonly string targetName = "TEST_TARGET_NAME";
+
+ [TestMethod]
+ public void TestConnectMagic()
+ {
+ var azureClient = new MockAzureClient();
+ var connectMagic = new ConnectMagic(azureClient);
+
+ // unrecognized input
+ connectMagic.Test($"invalid");
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.PrintConnectionStatus);
+
+ // valid input
+ connectMagic.Test(
+ @$"subscriptionId={subscriptionId}
+ resourceGroupName={resourceGroupName}
+ workspaceName={workspaceName}
+ storageAccountConnectionString={storageAccountConnectionString}");
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.Connect);
+ Assert.IsFalse(azureClient.ForceLogin);
+ Assert.AreEqual(azureClient.ConnectionString, storageAccountConnectionString);
+
+ // valid input with forced login
+ connectMagic.Test(
+ @$"login subscriptionId={subscriptionId}
+ resourceGroupName={resourceGroupName}
+ workspaceName={workspaceName}
+ storageAccountConnectionString={storageAccountConnectionString}");
+
+ Assert.IsTrue(azureClient.ForceLogin);
+ }
+
+ [TestMethod]
+ public void TestStatusMagic()
+ {
+ // no arguments - should print job list
+ var azureClient = new MockAzureClient();
+ var statusMagic = new StatusMagic(azureClient);
+ statusMagic.Test(string.Empty);
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.PrintJobList);
+
+ // single argument - should print job status
+ azureClient = new MockAzureClient();
+ statusMagic = new StatusMagic(azureClient);
+ statusMagic.Test($"{jobId}");
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.PrintJobStatus);
+ }
+
+ [TestMethod]
+ public void TestSubmitMagic()
+ {
+ // no arguments
+ var azureClient = new MockAzureClient();
+ var operationResolver = new MockOperationResolver();
+ var submitMagic = new SubmitMagic(operationResolver, azureClient);
+ submitMagic.Test(string.Empty);
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.SubmitJob);
+
+ // single argument
+ submitMagic.Test($"{operationName}");
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.SubmitJob);
+ Assert.IsTrue(azureClient.SubmittedJobs.Contains(operationName));
+ }
+
+ [TestMethod]
+ public void TestTargetMagic()
+ {
+ // no arguments - should print target list
+ var azureClient = new MockAzureClient();
+ var targetMagic = new TargetMagic(azureClient);
+ targetMagic.Test(string.Empty);
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.PrintTargetList);
+
+ // single argument - should set active target
+ azureClient = new MockAzureClient();
+ targetMagic = new TargetMagic(azureClient);
+ targetMagic.Test(targetName);
+ Assert.AreEqual(azureClient.LastAction, AzureClientAction.SetActiveTarget);
+ }
+ }
+
+ internal enum AzureClientAction
+ {
+ None,
+ Connect,
+ SetActiveTarget,
+ SubmitJob,
+ PrintConnectionStatus,
+ PrintJobList,
+ PrintJobStatus,
+ PrintTargetList,
+ }
+
+ public class MockAzureClient : IAzureClient
+ {
+ internal AzureClientAction LastAction = AzureClientAction.None;
+ internal string ConnectionString = string.Empty;
+ internal bool ForceLogin = false;
+ internal string ActiveTargetName = string.Empty;
+ internal List SubmittedJobs = new List();
+
+ public async Task SetActiveTargetAsync(IChannel channel, string targetName)
+ {
+ LastAction = AzureClientAction.SetActiveTarget;
+ ActiveTargetName = targetName;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task SubmitJobAsync(IChannel channel, IOperationResolver operationResolver, string operationName)
+ {
+ LastAction = AzureClientAction.SubmitJob;
+ SubmittedJobs.Add(operationName);
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task ConnectAsync(IChannel channel, string subscriptionId, string resourceGroupName, string workspaceName, string storageAccountConnectionString, bool forceLogin)
+ {
+ LastAction = AzureClientAction.Connect;
+ ConnectionString = storageAccountConnectionString;
+ ForceLogin = forceLogin;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task PrintConnectionStatusAsync(IChannel channel)
+ {
+ LastAction = AzureClientAction.PrintConnectionStatus;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task PrintJobListAsync(IChannel channel)
+ {
+ LastAction = AzureClientAction.PrintJobList;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task PrintJobStatusAsync(IChannel channel, string jobId)
+ {
+ LastAction = AzureClientAction.PrintJobStatus;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+
+ public async Task PrintTargetListAsync(IChannel channel)
+ {
+ LastAction = AzureClientAction.PrintTargetList;
+ return ExecuteStatus.Ok.ToExecutionResult();
+ }
+ }
+}
diff --git a/src/Tests/AzureClientTests.cs b/src/Tests/AzureClientTests.cs
index ba18985f1c..6c5eb88334 100644
--- a/src/Tests/AzureClientTests.cs
+++ b/src/Tests/AzureClientTests.cs
@@ -4,19 +4,37 @@
#nullable enable
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Microsoft.Quantum.IQSharp.AzureClient;
using System.Linq;
-using System.Collections.Generic;
+using Microsoft.Jupyter.Core;
+using Microsoft.Quantum.IQSharp.AzureClient;
namespace Tests.IQSharp
{
+ public static class AzureClientTestExtensions
+ {
+ }
+
[TestClass]
public class AzureClientTests
{
+ private readonly string subscriptionId = "TEST_SUBSCRIPTION_ID";
+ private readonly string resourceGroupName = "TEST_RESOURCE_GROUP_NAME";
+ private readonly string workspaceName = "TEST_WORKSPACE_NAME";
+ private readonly string storageAccountConnectionString = "TEST_CONNECTION_STRING";
+ private readonly string jobId = "TEST_JOB_ID";
+ private readonly string operationName = "TEST_OPERATION_NAME";
+ private readonly string targetName = "TEST_TARGET_NAME";
+
[TestMethod]
- public void TestNothing()
+ public void TestTargets()
{
- Assert.IsTrue(true);
+ var azureClient = new AzureClient();
+
+ var result = azureClient.SetActiveTargetAsync(new MockChannel(), targetName).GetAwaiter().GetResult();
+ Assert.IsTrue(result.Status == ExecuteStatus.Ok);
+
+ result = azureClient.PrintTargetListAsync(new MockChannel()).GetAwaiter().GetResult();
+ Assert.IsTrue(result.Status == ExecuteStatus.Error);
}
}
}
diff --git a/src/Tests/IQsharpEngineTests.cs b/src/Tests/IQsharpEngineTests.cs
index bd9d9b0f64..a4449ada95 100644
--- a/src/Tests/IQsharpEngineTests.cs
+++ b/src/Tests/IQsharpEngineTests.cs
@@ -15,6 +15,7 @@
using Newtonsoft.Json;
using System.Data;
+using Microsoft.Quantum.IQSharp.AzureClient;
#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
@@ -447,6 +448,12 @@ public void TestResolveMagic()
symbol = resolver.Resolve("%foo");
Assert.IsNull(symbol);
+
+ // AzureClient-provided commands
+ Assert.IsNotNull(resolver.Resolve("%connect"));
+ Assert.IsNotNull(resolver.Resolve("%status"));
+ Assert.IsNotNull(resolver.Resolve("%submit"));
+ Assert.IsNotNull(resolver.Resolve("%target"));
}
}
}
diff --git a/src/Tests/Mocks.cs b/src/Tests/Mocks.cs
index 11fcb4f645..84d06abe64 100644
--- a/src/Tests/Mocks.cs
+++ b/src/Tests/Mocks.cs
@@ -117,4 +117,12 @@ public IUpdatableDisplay DisplayUpdatable(object displayable)
public void Stdout(string message) => msgs.Add(message);
}
+
+ public class MockOperationResolver : IOperationResolver
+ {
+ public OperationInfo Resolve(string input)
+ {
+ return new OperationInfo(null, null);
+ }
+ }
}
diff --git a/src/Tests/Startup.cs b/src/Tests/Startup.cs
index c370d67d10..bc6ac12d15 100644
--- a/src/Tests/Startup.cs
+++ b/src/Tests/Startup.cs
@@ -9,6 +9,7 @@
using Microsoft.Extensions.Options;
using Microsoft.Jupyter.Core;
using Microsoft.Quantum.IQSharp;
+using Microsoft.Quantum.IQSharp.AzureClient;
using Microsoft.Quantum.IQSharp.Kernel;
namespace Tests.IQSharp
@@ -34,6 +35,7 @@ internal static ServiceProvider CreateServiceProvider(string workspaceFolder)
services.AddTelemetry();
services.AddIQSharp();
services.AddIQSharpKernel();
+ services.AddAzureClient();
var serviceProvider = services.BuildServiceProvider();
serviceProvider.GetRequiredService();
diff --git a/src/Tool/appsettings.json b/src/Tool/appsettings.json
index 44d378812c..5c8675d093 100644
--- a/src/Tool/appsettings.json
+++ b/src/Tool/appsettings.json
@@ -6,18 +6,18 @@
},
"AllowedHosts": "*",
"DefaultPackageVersions": [
- "Microsoft.Quantum.Compiler::0.11.2004.2825",
+ "Microsoft.Quantum.Compiler::0.11.2005.1420-beta",
- "Microsoft.Quantum.CsharpGeneration::0.11.2004.2825",
- "Microsoft.Quantum.Development.Kit::0.11.2004.2825",
- "Microsoft.Quantum.Simulators::0.11.2004.2825",
- "Microsoft.Quantum.Xunit::0.11.2004.2825",
+ "Microsoft.Quantum.CsharpGeneration::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Development.Kit::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Simulators::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Xunit::0.11.2005.1420-beta",
- "Microsoft.Quantum.Standard::0.11.2004.2825",
- "Microsoft.Quantum.Chemistry::0.11.2004.2825",
- "Microsoft.Quantum.Chemistry.Jupyter::0.11.2004.2825",
- "Microsoft.Quantum.Numerics::0.11.2004.2825",
+ "Microsoft.Quantum.Standard::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Chemistry::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Chemistry.Jupyter::0.11.2005.1420-beta",
+ "Microsoft.Quantum.Numerics::0.11.2005.1420-beta",
- "Microsoft.Quantum.Research::0.11.2004.2825"
+ "Microsoft.Quantum.Research::0.11.2005.1420-beta"
]
}