Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 157 additions & 51 deletions src/AzureClient/AzureClient.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/AzureClient/AzureClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Quantum.Client" Version="0.11.2005.1420-beta" />
<PackageReference Include="Microsoft.Azure.Quantum.Client" Version="0.11.2005.1924-beta" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
<PackageReference Include="Microsoft.Rest.ClientRuntime.Azure" Version="3.3.19" />
<PackageReference Include="System.Reactive" Version="4.3.2" />
Expand Down
110 changes: 110 additions & 0 deletions src/AzureClient/AzureEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable enable

using Microsoft.Quantum.Simulation.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;

namespace Microsoft.Quantum.IQSharp.AzureClient
{
internal enum AzureEnvironmentType { Production, Canary, Dogfood };

internal class AzureEnvironment
{
public string ClientId { get; private set; } = string.Empty;
public string Authority { get; private set; } = string.Empty;
public List<string> Scopes { get; private set; } = new List<string>();
public Uri? BaseUri { get; private set; }

private AzureEnvironment()
{
}

public static AzureEnvironment Create(string environment, string subscriptionId)
{
if (Enum.TryParse(environment, true, out AzureEnvironmentType environmentType))
{
switch (environmentType)
{
case AzureEnvironmentType.Production:
return Production();
case AzureEnvironmentType.Canary:
return Canary();
case AzureEnvironmentType.Dogfood:
return Dogfood(subscriptionId);
default:
throw new InvalidOperationException("Unexpected EnvironmentType value.");
}
}

return Production();
}

private static AzureEnvironment Production() =>
new AzureEnvironment()
{
ClientId = "84ba0947-6c53-4dd2-9ca9-b3694761521b", // QDK client ID
Authority = "https://login.microsoftonline.com/common",
Scopes = new List<string>() { "https://quantum.microsoft.com/Jobs.ReadWrite" },
BaseUri = new Uri("https://app-jobscheduler-prod.azurewebsites.net/"),
};

private static AzureEnvironment Dogfood(string subscriptionId) =>
new AzureEnvironment()
{
ClientId = "46a998aa-43d0-4281-9cbb-5709a507ac36", // QDK dogfood client ID
Authority = GetDogfoodAuthority(subscriptionId),
Scopes = new List<string>() { "api://dogfood.azure-quantum/Jobs.ReadWrite" },
BaseUri = new Uri("https://app-jobscheduler-test.azurewebsites.net/"),
};

private static AzureEnvironment Canary()
{
var canary = Production();
canary.BaseUri = new Uri("https://app-jobs-canarysouthcentralus.azurewebsites.net/");
return canary;
}

private static string GetDogfoodAuthority(string subscriptionId)
{
try
{
var armBaseUrl = "https://api-dogfood.resources.windows-int.net";
var requestUrl = $"{armBaseUrl}/subscriptions/{subscriptionId}?api-version=2018-01-01";

WebResponse? response = null;
try
{
response = WebRequest.Create(requestUrl).GetResponse();
}
catch (WebException webException)
{
response = webException.Response;
}

var authHeader = response.Headers["WWW-Authenticate"];
var headerParts = authHeader.Substring("Bearer ".Length).Split(',');
foreach (var headerPart in headerParts)
{
var parts = headerPart.Split("=", 2);
if (parts[0] == "authorization_uri")
{
var quotedAuthority = parts[1];
return quotedAuthority[1..^1];
}
}

throw new InvalidOperationException($"Dogfood authority not found in ARM header response for subscription ID {subscriptionId}.");
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to construct dogfood authority for subscription ID {subscriptionId}.", ex);
}
}
}
}
64 changes: 55 additions & 9 deletions src/AzureClient/IAzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,52 +66,98 @@ public interface IAzureClient
/// <summary>
/// Connects to the specified Azure Quantum workspace, first logging into Azure if necessary.
/// </summary>
/// <returns>
/// The list of execution targets available in the Azure Quantum workspace.
/// </returns>
public Task<ExecutionResult> ConnectAsync(
IChannel channel,
string subscriptionId,
string resourceGroupName,
string workspaceName,
string storageAccountConnectionString,
bool forceLogin = false);
bool refreshCredentials = false);

/// <summary>
/// Prints a string describing the current connection status.
/// Gets the current connection status to an Azure Quantum workspace.
/// </summary>
public Task<ExecutionResult> PrintConnectionStatusAsync(
/// <returns>
/// The list of execution targets available in the Azure Quantum workspace,
/// or an error if the Azure Quantum workspace connection has not yet been created.
/// </returns>
public Task<ExecutionResult> GetConnectionStatusAsync(
IChannel channel);

/// <summary>
/// Submits the specified Q# operation as a job to the currently active target.
/// </summary>
/// <returns>
/// Details of the submitted job, or an error if submission failed.
/// </returns>
public Task<ExecutionResult> SubmitJobAsync(
IChannel channel,
IOperationResolver operationResolver,
string operationName);

/// <summary>
/// Executes the specified Q# operation as a job to the currently active target
/// and waits for execution to complete before returning.
/// </summary>
/// <returns>
/// The result of the executed job, or an error if execution failed.
/// </returns>
public Task<ExecutionResult> ExecuteJobAsync(
IChannel channel,
IOperationResolver operationResolver,
string operationName);

/// <summary>
/// Sets the specified target for job submission.
/// </summary>
/// <returns>
/// Success if the target is valid, or an error if the target cannot be set.
/// </returns>
public Task<ExecutionResult> SetActiveTargetAsync(
IChannel channel,
string targetName);

/// <summary>
/// Prints the list of targets currently provisioned in the current workspace.
/// Gets the currently specified target for job submission.
/// </summary>
public Task<ExecutionResult> PrintTargetListAsync(
/// <returns>
/// The target name.
/// </returns>
public Task<ExecutionResult> GetActiveTargetAsync(
IChannel channel);

/// <summary>
/// Prints the job status corresponding to the given job ID.
/// Gets the result of a specified job.
/// </summary>
/// <returns>
/// The job result corresponding to the given job ID,
/// or for the most recently-submitted job if no job ID is provided.
/// </returns>
public Task<ExecutionResult> GetJobResultAsync(
IChannel channel,
string jobId);

/// <summary>
/// Gets the status of a specified job.
/// </summary>
public Task<ExecutionResult> PrintJobStatusAsync(
/// <returns>
/// The job status corresponding to the given job ID,
/// or for the most recently-submitted job if no job ID is provided.
/// </returns>
public Task<ExecutionResult> GetJobStatusAsync(
IChannel channel,
string jobId);

/// <summary>
/// Prints a list of all jobs in the current workspace.
/// Gets a list of all jobs in the current Azure Quantum workspace.
/// </summary>
public Task<ExecutionResult> PrintJobListAsync(
/// <returns>
/// A list of all jobs in the current workspace.
/// </returns>
public Task<ExecutionResult> GetJobListAsync(
IChannel channel);
}
}
53 changes: 31 additions & 22 deletions src/AzureClient/Magic/ConnectMagic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,64 +17,73 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
/// </summary>
public class ConnectMagic : AzureClientMagicBase
{
private const string
ParameterNameLogin = "login",
ParameterNameStorageAccountConnectionString = "storageAccountConnectionString",
ParameterNameSubscriptionId = "subscriptionId",
ParameterNameResourceGroupName = "resourceGroupName",
ParameterNameWorkspaceName = "workspaceName";
private const string ParameterNameRefresh = "refresh";
private const string ParameterNameStorageAccountConnectionString = "storageAccountConnectionString";
private const string ParameterNameSubscriptionId = "subscriptionId";
private const string ParameterNameResourceGroupName = "resourceGroupName";
private const string ParameterNameWorkspaceName = "workspaceName";

/// <summary>
/// Constructs a new magic command given an IAzureClient object.
/// Initializes a new instance of the <see cref="ConnectMagic"/> class.
/// </summary>
public ConnectMagic(IAzureClient azureClient) :
base(azureClient,
"connect",
/// <param name="azureClient">
/// The <see cref="IAzureClient"/> object to use for Azure functionality.
/// </param>
public ConnectMagic(IAzureClient azureClient)
: base(
azureClient,
"azure.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.

If the connection is successful, a list of the available execution targets
in the Azure Quantum workspace will be displayed.
".Dedent(),
Examples = new[]
{
@"
Print information about the current connection:
```
In []: %connect
Out[]: Connected to WORKSPACE_NAME
In []: %azure.connect
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
```
".Dedent(),

$@"
Connect to an Azure Quantum workspace:
```
In []: %connect {ParameterNameSubscriptionId}=SUBSCRIPTION_ID
In []: %azure.connect {ParameterNameSubscriptionId}=SUBSCRIPTION_ID
{ParameterNameResourceGroupName}=RESOURCE_GROUP_NAME
{ParameterNameWorkspaceName}=WORKSPACE_NAME
{ParameterNameStorageAccountConnectionString}=CONNECTION_STRING
Out[]: Connected to WORKSPACE_NAME
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
```
".Dedent(),

$@"
Connect to an Azure Quantum workspace and force a credential prompt:
```
In []: %connect {ParameterNameLogin}
In []: %azure.connect {ParameterNameRefresh}
{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
Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
```
Use the `{ParameterNameLogin}` option if you want to bypass any saved or cached
Use the `{ParameterNameRefresh}` option if you want to bypass any saved or cached
credentials when connecting to Azure.
".Dedent()
}
".Dedent(),
},
}) {}

/// <summary>
Expand All @@ -88,20 +97,20 @@ public override async Task<ExecutionResult> RunAsync(string input, IChannel chan
var storageAccountConnectionString = inputParameters.DecodeParameter<string>(ParameterNameStorageAccountConnectionString);
if (string.IsNullOrEmpty(storageAccountConnectionString))
{
return await AzureClient.PrintConnectionStatusAsync(channel);
return await AzureClient.GetConnectionStatusAsync(channel);
}

var subscriptionId = inputParameters.DecodeParameter<string>(ParameterNameSubscriptionId);
var resourceGroupName = inputParameters.DecodeParameter<string>(ParameterNameResourceGroupName);
var workspaceName = inputParameters.DecodeParameter<string>(ParameterNameWorkspaceName);
var forceLogin = inputParameters.DecodeParameter<bool>(ParameterNameLogin, defaultValue: false);
var refreshCredentials = inputParameters.DecodeParameter<bool>(ParameterNameRefresh, defaultValue: false);
return await AzureClient.ConnectAsync(
channel,
subscriptionId,
resourceGroupName,
workspaceName,
storageAccountConnectionString,
forceLogin);
refreshCredentials);
}
}
}
Loading