microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
32bcffe86a3647ddd45528ecb551f8feddd749ab

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/vscode/src/azure/networkRequests.ts

377lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import { log } from "qsharp-lang";
5import { EventType, getUserAgent, sendTelemetryEvent } from "../telemetry";
6
7export async function azureRequest(
8 uri: string,
9 token: string,
10 associationId?: string,
11 method = "GET",
12 body?: string,
13) {
14 const headers: [string, string][] = [
15 ["Content-Type", "application/json"],
16 ["x-ms-useragent", getUserAgent()],
17 ];
18
19 // For API simpilcity and storage back-compat, any api key is passed via the 'token' field.
20 if (token.startsWith("apiKey=")) {
21 headers.push(["x-ms-quantum-api-key", token.substring(7)]);
22 } else {
23 headers.push(["Authorization", `Bearer ${token}`]);
24 }
25
26 try {
27 log.debug(`Fetching ${uri} with method ${method}`);
28 log.trace("Request headers & body: ", headers, body);
29 const response = await fetch(uri, {
30 headers,
31 method,
32 body,
33 });
34
35 if (!response.ok) {
36 log.error("Azure request failed", response);
37 if (associationId) {
38 sendTelemetryEvent(
39 EventType.AzureRequestFailed,
40 {
41 reason: `request to azure returned code ${response.status}`,
42 associationId,
43 },
44 {},
45 );
46 }
47 throw await getAzureQuantumError(response);
48 }
49
50 log.debug(`Got response ${response.status} ${response.statusText}`);
51 const result = await response.json();
52 log.trace("Response value: ", result);
53
54 return result;
55 } catch (e) {
56 if (associationId) {
57 sendTelemetryEvent(
58 EventType.AzureRequestFailed,
59 { reason: `request to azure failed to return`, associationId },
60 {},
61 );
62 }
63 log.error(`Failed to fetch ${uri}: ${e}`);
64 throw new Error(getErrorMessage(e));
65 }
66}
67
68// Different enough to above to warrant it's own function
69export async function storageRequest(
70 uri: string,
71 method: string,
72 token?: string,
73 proxy?: string,
74 extraHeaders?: [string, string][],
75 body?: string | Uint8Array,
76 associationId?: string,
77) {
78 const headers: [string, string][] = [
79 ["x-ms-version", "2023-01-03"],
80 ["x-ms-date", new Date().toUTCString()],
81 ["x-ms-useragent", getUserAgent()],
82 ];
83 if (token) {
84 // For API simpilcity and storage back-compat, any api key is passed via the 'token' field.
85 if (token.startsWith("apiKey=")) {
86 headers.push(["x-ms-quantum-api-key", token.substring(7)]);
87 } else {
88 headers.push(["Authorization", `Bearer ${token}`]);
89 }
90 }
91
92 if (extraHeaders?.length) headers.push(...extraHeaders);
93 if (proxy) {
94 log.debug(`Setting x-proxy-to header to ${uri}`);
95 headers.push(["x-proxy-to", uri]);
96 uri = proxy;
97 }
98 try {
99 log.debug(`Fetching ${uri} with method ${method}`);
100 const response = await fetch(uri, { method, headers, body });
101 if (!response.ok) {
102 log.error("Storage request failed", response);
103 if (associationId) {
104 sendTelemetryEvent(
105 EventType.StorageRequestFailed,
106 {
107 reason: `request to storage on azure returned code ${response.status}`,
108 associationId,
109 },
110 {},
111 );
112 }
113 throw await getAzureStorageError(response);
114 }
115 log.debug(`Got response ${response.status} ${response.statusText}`);
116 return response;
117 } catch (e) {
118 log.error(`Failed to fetch ${uri}: ${e}`);
119 if (associationId) {
120 sendTelemetryEvent(
121 EventType.StorageRequestFailed,
122 {
123 reason: `request to storage on azure failed to return`,
124 associationId,
125 },
126 {},
127 );
128 }
129 throw new Error(getErrorMessage(e));
130 }
131}
132
133class AzureError extends Error {
134 constructor(message: string) {
135 super(message);
136 }
137}
138
139async function getAzureQuantumError(response: Response): Promise<AzureError> {
140 let error: { code: string; message: string } | undefined = undefined;
141 try {
142 const json = await response.json();
143 // Extract the error data if it conforms to the Azure Quantum error schema defined in
144 // https://github.com/Azure/azure-rest-api-specs/blob/957fd518388828b31126417415b04f859b95c586/specification/quantum/data-plane/Microsoft.Quantum/preview/2022-09-12-preview/quantum.json#L1186
145 if (json && json.error && json.error.code && json.error.message) {
146 error = json.error;
147 }
148 } catch {
149 /* empty */
150 }
151
152 let message;
153 if (error) {
154 message = `Azure Quantum request failed with status ${response.status}.\n${error.code}: ${error.message}`;
155 } else {
156 message = `Azure Quantum request failed with status ${response.status}.`;
157 }
158 return new AzureError(message);
159}
160
161function getAzureStorageError(response: Response): AzureError {
162 // Azure Storage appears to uses headers and xml responses to communicate error data,
163 // but we have not seen yet these in practice.
164 // https://github.com/Azure/azure-rest-api-specs/blob/eb06c34581dc6f56868ee9cc811a51f0e1a50770/specification/storage/data-plane/Microsoft.BlobStorage/preview/2021-12-02/blob.json#L75C30-L75C30
165 return new AzureError(
166 `Storage request failed with status ${response.status}.`,
167 );
168}
169
170// Generate a user friendly error message
171function getErrorMessage(e: any): string {
172 if (e instanceof AzureError) {
173 return e.message;
174 } else if (e instanceof Error) {
175 return `Request failed: ${e.message}`;
176 } else {
177 return `Request failed.`;
178 }
179}
180
181export class AzureUris {
182 static readonly apiVersion = "2020-01-01";
183 static readonly mgmtEndpoint = "https://management.azure.com";
184
185 static tenants() {
186 // https://learn.microsoft.com/en-us/rest/api/resources/tenants/list
187 return `${this.mgmtEndpoint}/tenants?api-version=${this.apiVersion}`;
188 }
189
190 static subscriptions() {
191 // https://learn.microsoft.com/en-us/rest/api/resources/subscriptions/list
192 return `${this.mgmtEndpoint}/subscriptions?api-version=${this.apiVersion}`;
193 }
194
195 static workspaces(subscriptionId: string) {
196 // https://github.com/Azure/azure-rest-api-specs/blob/main/specification/quantum/resource-manager/Microsoft.Quantum/preview/2022-01-10-preview/quantum.json#L221
197 return `${this.mgmtEndpoint}/subscriptions/${subscriptionId}/providers/Microsoft.Quantum/workspaces?api-version=2022-01-10-preview`;
198 }
199
200 static listKeys(
201 subscriptionId: string,
202 resourceGroupName: string,
203 workspaceName: string,
204 ) {
205 // https://github.com/Azure/azure-rest-api-specs/blob/main/specification/quantum/resource-manager/Microsoft.Quantum/preview/2023-11-13-preview/quantum.json#L419
206 return `${this.mgmtEndpoint}/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Quantum/workspaces/${workspaceName}/listKeys?api-version=2023-11-13-preview`;
207 }
208}
209
210export class QuantumUris {
211 readonly apiVersion = "2022-09-12-preview";
212
213 constructor(
214 public endpoint: string, // e.g. "https://westus.quantum.azure.com"
215 public id: string, // e.g. "/subscriptions/00000000-1111-2222-3333-444444444444/resourceGroups/quantumResourcegroup/providers/Microsoft.Quantum/Workspaces/quantumworkspace1"
216 ) {}
217
218 quotas() {
219 return `${this.endpoint}${this.id}/quotas?api-version=${this.apiVersion}`;
220 }
221
222 providerStatus() {
223 return `${this.endpoint}${this.id}/providerStatus?api-version=${this.apiVersion}`;
224 }
225
226 jobs(jobId?: string) {
227 return !jobId
228 ? `${this.endpoint}${this.id}/jobs?api-version=${this.apiVersion}`
229 : `${this.endpoint}${this.id}/jobs/${jobId}?api-version=${this.apiVersion}`;
230 }
231
232 // Needs to POST an application/json payload such as: {"containerName": "job-073064ed-2a47-11ee-b8e7-010101010000","blobName":"outputData"}
233 sasUri() {
234 return `${this.endpoint}${this.id}/storage/sasUri?api-version=${this.apiVersion}`;
235 }
236
237 storageProxy() {
238 return `${this.endpoint}${this.id}/storage/proxy?api-version=${this.apiVersion}`;
239 }
240}
241
242export class StorageUris {
243 // Requests use a Shared Access Signature. See https://learn.microsoft.com/en-us/rest/api/storageservices/service-sas-examples
244
245 // x-ms-date header should be present in format: Sun, 06 Nov 1994 08:49:37 GMT
246 // See https://learn.microsoft.com/en-us/rest/api/storageservices/representation-of-date-time-values-in-headers
247
248 readonly apiVersion = "2023-01-03"; // Pass as x-ms-version header (see https://learn.microsoft.com/en-us/rest/api/storageservices/versioning-for-the-azure-storage-services#authorize-requests-by-using-azure-ad-shared-key-or-shared-key-lite)
249
250 // Same for PUT, with a status code of 201 if successful
251 getContainer(storageAccount: string, container: string, sas: string) {
252 return `https://${storageAccount}.blob.core.windows.net/${container}?restype=container&${sas}`;
253 }
254
255 // Same for DELETE, with a status code of 202 if successful
256 // Also same URI for PUT, but must include the following headers:
257 // - x-ms-blob-type: BlockBlob
258 // - Content-Length: <n>
259 // It will return 201 if created.
260 getBlob(
261 storageAccount: string,
262 container: string,
263 blob: string,
264 sas: string,
265 ) {
266 return `https://${storageAccount}.blob.core.windows.net/${container}/${blob}?${sas}`;
267 }
268}
269
270// Put all the Response types in a namespace for easy importing
271
272// eslint-disable-next-line @typescript-eslint/no-namespace
273export namespace ResponseTypes {
274 export type Tenant = {
275 id: string;
276 tenantId: string;
277 displayName: string;
278 };
279
280 export type TenantList = {
281 value: Array<Tenant>;
282 };
283
284 export type Subscription = {
285 id: string;
286 subscriptionId: string;
287 tenantId: string;
288 displayName: string;
289 };
290
291 export type SubscriptionList = {
292 value: Array<Subscription>;
293 };
294
295 export type Provider = {
296 providerId: string;
297 providerSku: string;
298 provisioningState: string; // e.g. 'Succeeded'
299 resourceUsageId: string;
300 };
301
302 export type Workspace = {
303 id: string;
304 name: string;
305 location: string;
306 properties: {
307 providers: Array<Provider>;
308 provisioningState: string;
309 storageAccount: string; // e.g. "/subscriptions/<guid>/resourceGroups/<name>/providers/Microsoft.Storage/storageAccounts/<id>"
310 endpointUri: string; // e.g. "https://<workspace-name>.westus.quantum.azure.com". Note: workspace-name should be removed.
311 };
312 };
313
314 export type WorkspaceList = {
315 value: Array<Workspace>;
316 };
317
318 export type Quota = {
319 scope: string;
320 providerId: string;
321 period: string;
322 holds: number;
323 utilization: number;
324 limit: number;
325 };
326
327 export type QuotaList = {
328 nextLink: string | null;
329 value: Array<Quota>;
330 };
331
332 export type Target = {
333 id: string;
334 currentAvailability: "Available" | "Degraded" | "Unavailable";
335 averageQueueTime: number; // minutes
336 statusPage: string; // url
337 };
338
339 export type ProviderStatus = {
340 id: string;
341 currentAvailability: "Available" | "Degraded" | "Unavailable";
342 targets: Array<Target>;
343 };
344
345 export type ProviderStatusList = {
346 nextLink: string | null;
347 value: Array<ProviderStatus>;
348 };
349
350 export type Job = {
351 id: string;
352 name: string;
353 target: string;
354 containerUri: string;
355 inputDataUri: string;
356 outputDataUri: string;
357 inputDataFormat: string;
358 outputDataFormat: string;
359 inputParams: any;
360 status: "Waiting" | "Executing" | "Succeeded" | "Failed" | "Cancelled";
361 creationTime: string;
362 beginExecutionTime: string;
363 endExecutionTime: string;
364 cancellationTime?: string;
365 costEstimate?: any;
366 errorData?: any;
367 };
368
369 export type JobList = {
370 nextLink: string | null;
371 value: Array<Job>;
372 };
373
374 export type SasUri = {
375 sasUri: string;
376 };
377}
378