Skip to content
This repository was archived by the owner on May 13, 2025. It is now read-only.

Commit 204407d

Browse files
warren-jonesbavneetsingh16
authored andcommitted
[Quantum] Use Post Storage URI and SAS token like Quantum Python SDK (27643) (Azure#8242)
* Temporarily append '.dev3' to version number * Calling upload-blob with return_sas_token = True * Incorporated azure-quantum-python functions * Adding a SAS token to the storage container URL * Moved an import statement from _storage.py to job.py * Fixed style check violations * Deleted an extraneous comment * Added a unit test * Expanded the unit test * Made changes to test_submit * Made changes to test_submit * Updated test_provider_sku_list * Added experimental Python SDK code to run a Qiskit input file * Added test input files and simplified the job submit code * Rearranged the submit code to use old param validation * Got it working with imported Python SDK methods * Un-did changes to _storage.py and _client_factory.py * Fixed Pylint rule violations * Experimenting with ways to suppress the expected azure.identity CredentialUnavailableError messages * Deleted unused import statements * Copied azure-quantum-python files to the CLI repo * Added missing SDK files and fixed another import * Added the azure.identity files to vendored_sdks * Fixed another azure.identity import * Deleted a commented-out line * Moved the azure-quantum-python files to the vendored_sdks folder * Deleted commented-out lines * Removed 'microsoft.qc' from the default test provider/SKU list and removed '.dev3' from the version number * Made test changes like in branch 29126-remove-resource-estimator-references (PR 8388) * Add '.dev3' suffix to version number for final testing * Remove '.dev3' from the version numbers
1 parent c46e477 commit 204407d

117 files changed

Lines changed: 18363 additions & 140 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/quantum/azext_quantum/_storage.py

Lines changed: 0 additions & 128 deletions
This file was deleted.

src/quantum/azext_quantum/operations/job.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
import uuid
1414
import knack.log
1515

16-
from azure.cli.command_modules.storage.operations.account import show_storage_account_connection_string
1716
from azure.cli.core.azclierror import (FileOperationError, AzureInternalError,
1817
InvalidArgumentValueError, AzureResponseError,
1918
RequiredArgumentMissingError)
2019

21-
from .._storage import create_container, upload_blob
22-
20+
from ..vendored_sdks.azure_quantum_python.workspace import Workspace
21+
from ..vendored_sdks.azure_quantum_python.storage import upload_blob
22+
from ..vendored_sdks.azure_storage_blob import ContainerClient
2323
from .._client_factory import cf_jobs
2424
from .._list_helper import repack_response_json
2525
from .workspace import WorkspaceInfo
@@ -324,15 +324,19 @@ def submit(cmd, resource_group_name, workspace_name, location, target_id, job_in
324324
raise RequiredArgumentMissingError("No storage account specified or linked with workspace.")
325325
storage = ws.properties.storage_account.split('/')[-1]
326326
job_id = str(uuid.uuid4())
327-
container_name = "quantum-job-" + job_id
328-
connection_string_dict = show_storage_account_connection_string(cmd, resource_group_name, storage)
329-
connection_string = connection_string_dict["connectionString"]
330-
container_client = create_container(connection_string, container_name)
331327
blob_name = "inputData"
332328

329+
resource_id = "/subscriptions/" + ws_info.subscription + "/resourceGroups/" + ws_info.resource_group + "/providers/Microsoft.Quantum/Workspaces/" + ws_info.name
330+
workspace = Workspace(resource_id=resource_id, location=location)
331+
332+
knack_logger.warning("Getting Azure credential token...")
333+
container_uri = workspace.get_container_uri(job_id=job_id)
334+
container_client = ContainerClient.from_container_url(container_uri)
335+
333336
knack_logger.warning("Uploading input data...")
334337
try:
335-
blob_uri = upload_blob(container_client, blob_name, content_type, content_encoding, blob_data, False)
338+
blob_uri = upload_blob(container_client, blob_name, content_type, content_encoding, blob_data, return_sas_token=False)
339+
logger.debug(" - blob uri: %s", blob_uri)
336340
except Exception as e:
337341
# Unexplained behavior:
338342
# QIR bitcode input and QIO (gzip) input data get UnicodeDecodeError on jobs run in tests using
@@ -343,9 +347,6 @@ def submit(cmd, resource_group_name, workspace_name, location, target_id, job_in
343347
error_msg += f"\nReason: {e.reason}"
344348
raise AzureResponseError(error_msg) from e
345349

346-
start_of_blob_name = blob_uri.find(blob_name)
347-
container_uri = blob_uri[0:start_of_blob_name - 1]
348-
349350
# Combine separate command-line parameters (like shots, target_capability, and entry_point) with job_params
350351
if job_params is None:
351352
job_params = {}

src/quantum/azext_quantum/tests/latest/test_quantum_jobs.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,26 @@ def test_submit(self):
212212
self.cmd(f"az quantum workspace create -g {test_resource_group} -w {test_workspace_temp} -l {test_location} -a {test_storage} -r {test_provider_sku_list} --skip-autoadd")
213213
self.cmd(f"az quantum workspace set -g {test_resource_group} -w {test_workspace_temp} -l {test_location}")
214214

215+
# Submit a job to Rigetti and look for SAS tokens in URIs in the output
216+
results = self.cmd("az quantum job submit -t rigetti.sim.qvm --job-input-format rigetti.quil.v1 -t rigetti.sim.qvm --job-input-file src/quantum/azext_quantum/tests/latest/input_data/bell-state.quil --job-output-format rigetti.quil-results.v1 -o json").get_output_in_json()
217+
self.assertIn("?sv=", results["containerUri"])
218+
self.assertIn("&st=", results["containerUri"])
219+
self.assertIn("&se=", results["containerUri"])
220+
self.assertIn("&sp=", results["containerUri"])
221+
self.assertIn("&sig=", results["containerUri"])
222+
223+
self.assertIn("?sv=", results["inputDataUri"])
224+
self.assertIn("&st=", results["inputDataUri"])
225+
self.assertIn("&se=", results["inputDataUri"])
226+
self.assertIn("&sp=", results["inputDataUri"])
227+
self.assertIn("&sig=", results["inputDataUri"])
228+
229+
self.assertIn("?sv=", results["outputDataUri"])
230+
self.assertIn("&st=", results["outputDataUri"])
231+
self.assertIn("&se=", results["outputDataUri"])
232+
self.assertIn("&sp=", results["outputDataUri"])
233+
self.assertIn("&sig=", results["outputDataUri"])
234+
215235
# Run a Quil pass-through job on Rigetti
216236
results = self.cmd("az quantum run -t rigetti.sim.qvm --job-input-format rigetti.quil.v1 -t rigetti.sim.qvm --job-input-file src/quantum/azext_quantum/tests/latest/input_data/bell-state.quil --job-output-format rigetti.quil-results.v1 -o json").get_output_in_json()
217237
self.assertIn("ro", results)
@@ -233,7 +253,6 @@ def test_submit(self):
233253

234254
results = str(self.cmd("az quantum job list --skip 1 -o json").get_output_in_json())
235255
self.assertIn("ionq", results)
236-
self.assertTrue("rigetti" not in results)
237256

238257
results = str(self.cmd("az quantum job list --orderby Target --skip 1 -o json").get_output_in_json())
239258
self.assertIn("rigetti", results)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
"""Credentials for Azure SDK clients."""
6+
7+
from ._auth_record import AuthenticationRecord
8+
from ._exceptions import AuthenticationRequiredError, CredentialUnavailableError
9+
from ._constants import AzureAuthorityHosts, KnownAuthorities
10+
from ._credentials import (
11+
AuthorizationCodeCredential,
12+
AzureDeveloperCliCredential,
13+
AzureCliCredential,
14+
AzurePowerShellCredential,
15+
CertificateCredential,
16+
ChainedTokenCredential,
17+
ClientAssertionCredential,
18+
ClientSecretCredential,
19+
DefaultAzureCredential,
20+
DeviceCodeCredential,
21+
EnvironmentCredential,
22+
InteractiveBrowserCredential,
23+
ManagedIdentityCredential,
24+
OnBehalfOfCredential,
25+
SharedTokenCacheCredential,
26+
UsernamePasswordCredential,
27+
VisualStudioCodeCredential,
28+
WorkloadIdentityCredential,
29+
AzurePipelinesCredential,
30+
)
31+
from ._persistent_cache import TokenCachePersistenceOptions
32+
from ._bearer_token_provider import get_bearer_token_provider
33+
34+
35+
__all__ = [
36+
"AuthenticationRecord",
37+
"AuthenticationRequiredError",
38+
"AuthorizationCodeCredential",
39+
"AzureAuthorityHosts",
40+
"AzureCliCredential",
41+
"AzureDeveloperCliCredential",
42+
"AzurePipelinesCredential",
43+
"AzurePowerShellCredential",
44+
"CertificateCredential",
45+
"ChainedTokenCredential",
46+
"ClientAssertionCredential",
47+
"ClientSecretCredential",
48+
"CredentialUnavailableError",
49+
"DefaultAzureCredential",
50+
"DeviceCodeCredential",
51+
"EnvironmentCredential",
52+
"InteractiveBrowserCredential",
53+
"KnownAuthorities",
54+
"OnBehalfOfCredential",
55+
"ManagedIdentityCredential",
56+
"SharedTokenCacheCredential",
57+
"TokenCachePersistenceOptions",
58+
"UsernamePasswordCredential",
59+
"VisualStudioCodeCredential",
60+
"WorkloadIdentityCredential",
61+
"get_bearer_token_provider",
62+
]
63+
64+
from ._version import VERSION
65+
66+
__version__ = VERSION
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
import json
6+
7+
8+
SUPPORTED_VERSIONS = {"1.0"}
9+
10+
11+
class AuthenticationRecord:
12+
"""Non-secret account information for an authenticated user
13+
14+
This class enables :class:`DeviceCodeCredential` and :class:`InteractiveBrowserCredential` to access
15+
previously cached authentication data. Applications shouldn't construct instances of this class. They should
16+
instead acquire one from a credential's **authenticate** method, such as
17+
:func:`InteractiveBrowserCredential.authenticate`. See the user_authentication sample for more details.
18+
19+
:param str tenant_id: The tenant the account should authenticate in.
20+
:param str client_id: The client ID of the application which performed the original authentication.
21+
:param str authority: The authority host used to authenticate the account.
22+
:param str home_account_id: A unique identifier of the account.
23+
:param str username: The user principal or service principal name of the account.
24+
"""
25+
26+
def __init__(self, tenant_id: str, client_id: str, authority: str, home_account_id: str, username: str) -> None:
27+
self._authority = authority
28+
self._client_id = client_id
29+
self._home_account_id = home_account_id
30+
self._tenant_id = tenant_id
31+
self._username = username
32+
33+
@property
34+
def authority(self) -> str:
35+
"""The authority host used to authenticate the account.
36+
37+
:rtype: str
38+
"""
39+
return self._authority
40+
41+
@property
42+
def client_id(self) -> str:
43+
"""The client ID of the application which performed the original authentication.
44+
45+
:rtype: str
46+
"""
47+
return self._client_id
48+
49+
@property
50+
def home_account_id(self) -> str:
51+
"""A unique identifier of the account.
52+
53+
:rtype: str
54+
"""
55+
return self._home_account_id
56+
57+
@property
58+
def tenant_id(self) -> str:
59+
"""The tenant the account should authenticate in.
60+
61+
:rtype: str
62+
"""
63+
return self._tenant_id
64+
65+
@property
66+
def username(self) -> str:
67+
"""The user principal or service principal name of the account.
68+
69+
:rtype: str
70+
"""
71+
return self._username
72+
73+
@classmethod
74+
def deserialize(cls, data: str) -> "AuthenticationRecord":
75+
"""Deserialize a record.
76+
77+
:param str data: A serialized record.
78+
:return: The deserialized record.
79+
:rtype: ~azure.identity.AuthenticationRecord
80+
"""
81+
82+
deserialized = json.loads(data)
83+
84+
version = deserialized.get("version")
85+
if version not in SUPPORTED_VERSIONS:
86+
raise ValueError(
87+
'Unexpected version "{}". This package supports these versions: {}'.format(version, SUPPORTED_VERSIONS)
88+
)
89+
90+
return cls(
91+
authority=deserialized["authority"],
92+
client_id=deserialized["clientId"],
93+
home_account_id=deserialized["homeAccountId"],
94+
tenant_id=deserialized["tenantId"],
95+
username=deserialized["username"],
96+
)
97+
98+
def serialize(self) -> str:
99+
"""Serialize the record.
100+
101+
:return: The serialized record.
102+
:rtype: str
103+
"""
104+
105+
record = {
106+
"authority": self._authority,
107+
"clientId": self._client_id,
108+
"homeAccountId": self._home_account_id,
109+
"tenantId": self._tenant_id,
110+
"username": self._username,
111+
"version": "1.0",
112+
}
113+
114+
return json.dumps(record)

0 commit comments

Comments
 (0)