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
Show all changes
25 commits
Select commit Hold shift + click to select a range
9633735
Support resource ID in %azure.connect
rmshaffer Jun 17, 2020
29fc3cf
Update parameter names to match standalone executable
rmshaffer Jun 18, 2020
914270d
Merge branch 'rmshaffer/azure-mock-tests' into rmshaffer/linked-storage
rmshaffer Jun 19, 2020
f71c7ad
Update Python API and tests
rmshaffer Jun 19, 2020
be26b0c
Merge branch 'rmshaffer/azure-mock-tests' into rmshaffer/linked-storage
rmshaffer Jun 19, 2020
be57cc8
Use named parameter for resource ID
rmshaffer Jun 19, 2020
6f3fb72
Avoid calling Regex.IsMatch
Jun 19, 2020
b68675d
Address PR feedback and improve tests
rmshaffer Jun 19, 2020
ec13639
Empty commit to re-trigger CI build
rmshaffer Jun 19, 2020
5f48066
Speed up tests, enable debug logging
rmshaffer Jun 20, 2020
b8d7e36
Merge branch 'rmshaffer/azure-mock-tests' into rmshaffer/linked-storage
rmshaffer Jun 22, 2020
fff1065
Merge branch 'feature/azure-client' into rmshaffer/linked-storage
rmshaffer Jun 22, 2020
7cccc7c
Make NuGet.config available to conda package
rmshaffer Jun 23, 2020
4effcf1
Add prerelease feed for non-release builds
rmshaffer Jun 23, 2020
5c361f7
Merge branch 'feature/azure-client' into rmshaffer/linked-storage
rmshaffer Jun 23, 2020
d7ba3e1
Copy NuGet config during build step
rmshaffer Jun 23, 2020
805ce6d
Merge branch 'feature/azure-client' into rmshaffer/linked-storage
rmshaffer Jun 23, 2020
4f326ae
Fix bad merge with job filter changes
rmshaffer Jun 23, 2020
fcb7588
Copy NuGet.config to home folder
rmshaffer Jun 23, 2020
3891f08
Copy NuGet.config in both jobs
rmshaffer Jun 23, 2020
39d7644
Write prerelease NuGet directly
rmshaffer Jun 23, 2020
6661e23
Construct RegEx object once
rmshaffer Jun 23, 2020
3f5486c
Remove line breaks
rmshaffer Jun 23, 2020
8a6db12
Write prerelease NuGet into target directory
rmshaffer Jun 23, 2020
940284e
Put prerelease NuGet back in test.ps1 for now
rmshaffer Jun 23, 2020
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
2 changes: 1 addition & 1 deletion build/test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function Test-Python {
Write-Host "##[info]Testing Python inside $testFolder"
Push-Location (Join-Path $PSScriptRoot $testFolder)
python --version
pytest -v
pytest -v --log-level=Debug
Pop-Location

if ($LastExitCode -ne 0) {
Expand Down
2 changes: 0 additions & 2 deletions conda-recipes/iqsharp/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ if ($IsWindows) {
$RepoRoot = Resolve-Path (Join-Path $PSScriptRoot "../..");
$ArtifactRoot = Join-Path $RepoRoot "drops";
$SelfContainedDirectory = Join-Path $ArtifactRoot (Join-Path "selfcontained" $RuntimeID)
$NugetsDirectory = Join-Path $ArtifactRoot "nugets"
$NugetConfig = Resolve-Path (Join-Path $PSScriptRoot "NuGet.config");

$TargetDirectory = (Join-Path (Join-Path $Env:PREFIX "opt") "iqsharp");

Expand Down
9 changes: 8 additions & 1 deletion conda-recipes/iqsharp/test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ $failed = $false;

$Env:IQSHARP_PACKAGE_SOURCE = "$Env:NUGET_OUTDIR"

# Add the prerelease NuGet feed if this isn't a release build.
if ("$Env:BUILD_RELEASETYPE" -ne "release") {
$NuGetDirectory = Resolve-Path ~
Write-Host "## Writing prerelease NuGet config to $NuGetDirectory ##"
echo "<?xml version=""1.0"" encoding=""utf-8""?><configuration><packageSources><add key=""qdk-alpha"" value=""https://pkgs.dev.azure.com/ms-quantum-public/Microsoft Quantum (public)/_packaging/alpha/nuget/v3/index.json"" protocolVersion=""3"" /></packageSources></configuration>" > $NuGetDirectory/NuGet.Config
}

# Check that iqsharp is installed as a Jupyter kernel.
$kernels = jupyter kernelspec list --json | ConvertFrom-Json;
if ($null -eq $kernels.kernelspecs.iqsharp) {
Expand All @@ -13,7 +20,7 @@ if ($null -eq $kernels.kernelspecs.iqsharp) {
jupyter kernelspec list
}


# Run the kernel unit tests.
Push-Location $PSScriptRoot
python test.py
if ($LastExitCode -ne 0) {
Expand Down
27 changes: 21 additions & 6 deletions src/AzureClient/AzureClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,36 @@ public async Task<ExecutionResult> ConnectAsync(IChannel channel,
string storageAccountConnectionString,
bool refreshCredentials = false)
{
ConnectionString = storageAccountConnectionString;

var azureEnvironment = AzureEnvironment.Create(subscriptionId);
ActiveWorkspace = await azureEnvironment.GetAuthenticatedWorkspaceAsync(channel, resourceGroupName, workspaceName, refreshCredentials);
if (ActiveWorkspace == null)
IAzureWorkspace? workspace = null;
try
{
workspace = await azureEnvironment.GetAuthenticatedWorkspaceAsync(channel, resourceGroupName, workspaceName, refreshCredentials);
}
catch (Exception e)
{
channel.Stderr($"The connection to the Azure Quantum workspace could not be completed. Please check the provided parameters and try again.");
channel.Stderr($"Error details: {e.Message}");
return AzureClientError.WorkspaceNotFound.ToExecutionResult();
}

if (workspace == null)
{
return AzureClientError.AuthenticationFailed.ToExecutionResult();
}

AvailableProviders = await ActiveWorkspace.GetProvidersAsync();
if (AvailableProviders == null)
var providers = await workspace.GetProvidersAsync();
if (providers == null)
{
return AzureClientError.WorkspaceNotFound.ToExecutionResult();
}

ActiveWorkspace = workspace;
AvailableProviders = providers;
ConnectionString = storageAccountConnectionString;
ActiveTarget = null;
MostRecentJobId = string.Empty;

channel.Stdout($"Connected to Azure Quantum workspace {ActiveWorkspace.Name}.");

if (ValidExecutionTargets.Count() == 0)
Expand Down
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.2006.403" />
<PackageReference Include="Microsoft.Azure.Quantum.Client" Version="0.11.2006.1615-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
111 changes: 78 additions & 33 deletions src/AzureClient/Magic/ConnectMagic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Jupyter.Core;
Expand All @@ -19,10 +20,11 @@ namespace Microsoft.Quantum.IQSharp.AzureClient
public class ConnectMagic : AzureClientMagicBase
{
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";
private const string ParameterNameStorageAccountConnectionString = "storage";
private const string ParameterNameSubscriptionId = "subscription";
private const string ParameterNameResourceGroupName = "resourceGroup";
private const string ParameterNameWorkspaceName = "workspace";
private const string ParameterNameResourceId = "resourceId";

/// <summary>
/// Initializes a new instance of the <see cref="ConnectMagic"/> class.
Expand All @@ -36,54 +38,73 @@ public ConnectMagic(IAzureClient azureClient)
"azure.connect",
new Documentation
{
Summary = "Connects to an Azure workspace or displays current connection status.",
Summary = "Connects to an Azure Quantum 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.
as specified by the resource ID of the workspace or by a combination of
subscription ID, resource group name, and workspace name.

If the connection is successful, a list of the available execution targets
If the connection is successful, a list of the available Q# execution targets
in the Azure Quantum workspace will be displayed.
".Dedent(),
Examples = new[]
{
@"
Print information about the current connection:
$@"
Connect to an Azure Quantum workspace using its resource ID:
```
In []: %azure.connect
In []: %azure.connect {ParameterNameResourceId}=""/subscriptions/f846b2bd-d0e2-4a1d-8141-4c6944a9d387/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Quantum/Workspaces/WORKSPACE_NAME""
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
<list of Q# execution targets available in the Azure Quantum workspace>
```
".Dedent(),

$@"
Connect to an Azure Quantum workspace:
Connect to an Azure Quantum workspace using its resource ID and a storage account connection string,
which is required for workspaces that do not have a linked storage account:
```
In []: %azure.connect {ParameterNameSubscriptionId}=SUBSCRIPTION_ID
{ParameterNameResourceGroupName}=RESOURCE_GROUP_NAME
{ParameterNameWorkspaceName}=WORKSPACE_NAME
{ParameterNameStorageAccountConnectionString}=CONNECTION_STRING
In []: %azure.connect {ParameterNameResourceId}=""/subscriptions/f846b2bd-d0e2-4a1d-8141-4c6944a9d387/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Quantum/Workspaces/WORKSPACE_NAME""
{ParameterNameStorageAccountConnectionString}=""STORAGE_ACCOUNT_CONNECTION_STRING""
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
<list of Q# execution targets available in the Azure Quantum workspace>
```
".Dedent(),

$@"
Connect to an Azure Quantum workspace and force a credential prompt:
Connect to an Azure Quantum workspace using individual parameters:
```
In []: %azure.connect {ParameterNameRefresh}
{ParameterNameSubscriptionId}=SUBSCRIPTION_ID
{ParameterNameResourceGroupName}=RESOURCE_GROUP_NAME
{ParameterNameWorkspaceName}=WORKSPACE_NAME
{ParameterNameStorageAccountConnectionString}=CONNECTION_STRING
In []: %azure.connect {ParameterNameSubscriptionId}=""SUBSCRIPTION_ID""
{ParameterNameResourceGroupName}=""RESOURCE_GROUP_NAME""
{ParameterNameWorkspaceName}=""WORKSPACE_NAME""
{ParameterNameStorageAccountConnectionString}=""STORAGE_ACCOUNT_CONNECTION_STRING""
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of Q# execution targets available in the Azure Quantum workspace>
```
The `{ParameterNameStorageAccountConnectionString}` parameter is necessary only if the
specified Azure Quantum workspace was not linked to a storage account at creation time.
".Dedent(),

$@"
Connect to an Azure Quantum workspace and force a credential prompt using
the `{ParameterNameRefresh}` option:
```
In []: %azure.connect {ParameterNameRefresh} {ParameterNameResourceId}=""/subscriptions/f846b2bd-d0e2-4a1d-8141-4c6944a9d387/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Quantum/Workspaces/WORKSPACE_NAME""
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 Azure Quantum workspace WORKSPACE_NAME.
<list of targets available in the Azure Quantum workspace>
<list of Q# execution targets available in the Azure Quantum workspace>
```
Use the `{ParameterNameRefresh}` option if you want to bypass any saved or cached
The `{ParameterNameRefresh}` option bypasses any saved or cached
credentials when connecting to Azure.
".Dedent(),

@"
Print information about the current connection:
```
In []: %azure.connect
Out[]: Connected to Azure Quantum workspace WORKSPACE_NAME.
<list of Q# execution targets available in the Azure Quantum workspace>
```
".Dedent(),
},
}) {}

Expand All @@ -94,16 +115,40 @@ credentials when connecting to Azure.
public override async Task<ExecutionResult> RunAsync(string input, IChannel channel, CancellationToken cancellationToken)
{
var inputParameters = ParseInputParameters(input);

var storageAccountConnectionString = inputParameters.DecodeParameter<string>(ParameterNameStorageAccountConnectionString);
if (string.IsNullOrEmpty(storageAccountConnectionString))
if (!inputParameters.Any())
{
return await AzureClient.GetConnectionStatusAsync(channel);
}

var subscriptionId = inputParameters.DecodeParameter<string>(ParameterNameSubscriptionId);
var resourceGroupName = inputParameters.DecodeParameter<string>(ParameterNameResourceGroupName);
var workspaceName = inputParameters.DecodeParameter<string>(ParameterNameWorkspaceName);
var resourceId = inputParameters.DecodeParameter<string>(ParameterNameResourceId, defaultValue: string.Empty);
var subscriptionId = string.Empty;
var resourceGroupName = string.Empty;
var workspaceName = string.Empty;

// A valid resource ID looks like:
// /subscriptions/f846b2bd-d0e2-4a1d-8141-4c6944a9d387/resourceGroups/RESOURCE_GROUP_NAME/providers/Microsoft.Quantum/Workspaces/WORKSPACE_NAME
var match = Regex.Match(resourceId,
@"^/subscriptions/([a-fA-F0-9-]*)/resourceGroups/([^\s/]*)/providers/Microsoft\.Quantum/Workspaces/([^\s/]*)$");
if (match.Success)
{
// match.Groups will be a GroupCollection containing four Group objects:
// -> match.Groups[0]: The full resource ID for the Azure Quantum workspace
// -> match.Groups[1]: The Azure subscription ID
// -> match.Groups[2]: The Azure resource group name
// -> match.Groups[3]: The Azure Quantum workspace name
subscriptionId = match.Groups[1].Value;
resourceGroupName = match.Groups[2].Value;
workspaceName = match.Groups[3].Value;
}
else
{
// look for each of the parameters individually
subscriptionId = inputParameters.DecodeParameter<string>(ParameterNameSubscriptionId, defaultValue: string.Empty);
resourceGroupName = inputParameters.DecodeParameter<string>(ParameterNameResourceGroupName, defaultValue: string.Empty);
workspaceName = inputParameters.DecodeParameter<string>(ParameterNameWorkspaceName, defaultValue: string.Empty);
}

var storageAccountConnectionString = inputParameters.DecodeParameter<string>(ParameterNameStorageAccountConnectionString, defaultValue: string.Empty);
var refreshCredentials = inputParameters.DecodeParameter<bool>(ParameterNameRefresh, defaultValue: false);
return await AzureClient.ConnectAsync(
channel,
Expand All @@ -114,4 +159,4 @@ public override async Task<ExecutionResult> RunAsync(string input, IChannel chan
refreshCredentials);
}
}
}
}
6 changes: 3 additions & 3 deletions src/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
<PackageReference Include="Microsoft.Quantum.Compiler" Version="0.11.2006.403" />
<PackageReference Include="Microsoft.Quantum.CsharpGeneration" Version="0.11.2006.403" />
<PackageReference Include="Microsoft.Quantum.Simulators" Version="0.11.2006.403" />
<PackageReference Include="Microsoft.Quantum.Compiler" Version="0.11.2006.1615-beta" />
<PackageReference Include="Microsoft.Quantum.CsharpGeneration" Version="0.11.2006.1615-beta" />
<PackageReference Include="Microsoft.Quantum.Simulators" Version="0.11.2006.1615-beta" />
<PackageReference Include="NuGet.Resolver" Version="5.1.0" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
</ItemGroup>
Expand Down
8 changes: 5 additions & 3 deletions src/Jupyter/Magic/AbstractMagic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public static Dictionary<string, string> ParseInputParameters(string input, stri
var regex = new Regex(@"(\{.*\})|[^\s""]+(?:\s*=\s*)(?:""[^""]*""|[^\s""]*)*|[^\s""]+(?:""[^""]*""[^\s""]*)*|(?:""[^""]*""[^\s""]*)+");
var args = regex.Matches(input).Select(match => match.Value);

var regexBeginEndQuotes = new Regex(@"^['""]|['""]$");

// 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.Any() &&
Expand All @@ -102,7 +104,7 @@ public static Dictionary<string, string> ParseInputParameters(string input, stri
!string.IsNullOrEmpty(firstParameterInferredName))
{
using var writer = new StringWriter();
Json.Serializer.Serialize(writer, args.First());
Json.Serializer.Serialize(writer, regexBeginEndQuotes.Replace(args.First(), string.Empty));
inputParameters[firstParameterInferredName] = writer.ToString();
args = args.Skip(1);
}
Expand All @@ -123,14 +125,14 @@ public static Dictionary<string, string> ParseInputParameters(string input, stri
foreach (string arg in args)
{
var tokens = arg.Split("=", 2);
var key = tokens[0].Trim();
var key = regexBeginEndQuotes.Replace(tokens[0].Trim(), string.Empty);
var value = tokens.Length switch
{
// If there was no value provided explicitly, treat it as an implicit "true" value
1 => true as object,

// Trim whitespace and also enclosing single-quotes or double-quotes before returning
2 => Regex.Replace(tokens[1].Trim(), @"^['""]|['""]$", string.Empty) as object,
2 => regexBeginEndQuotes.Replace(tokens[1].Trim(), string.Empty) as object,

// We called arg.Split("=", 2), so there should never be more than 2
_ => throw new InvalidOperationException()
Expand Down
2 changes: 1 addition & 1 deletion src/Python/qsharp/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,6 @@ def output(jobId : str = '', **params) -> Dict:
return result

def jobs(filter : str = '', **params) -> List[AzureJob]:
result = qsharp.client._execute_magic(f"azure.jobs {filter}", raise_on_stderr=False, **params)
result = qsharp.client._execute_magic(f"azure.jobs \"{filter}\"", raise_on_stderr=False, **params)
if "error_code" in result: raise AzureError(result)
return [AzureJob(job) for job in result]
Loading