From 2ac4a9555c5b22a9c7e05bd1f30fb5cae46c15ce Mon Sep 17 00:00:00 2001 From: xiaonlimsft Date: Thu, 8 Oct 2020 21:16:56 +0800 Subject: [PATCH] Continuation Token wrapped with Error when Recursive Acl call is interrupted (#11716) --- .../review/storage-file-datalake.api.md | 7 ++++ .../storage-file-datalake/src/clients.ts | 38 +++++++++++-------- .../src/index.browser.ts | 1 + .../storage-file-datalake/src/index.ts | 1 + .../src/utils/DataLakeAclChangeFailedError.ts | 36 ++++++++++++++++++ 5 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 sdk/storage/storage-file-datalake/src/utils/DataLakeAclChangeFailedError.ts diff --git a/sdk/storage/storage-file-datalake/review/storage-file-datalake.api.md b/sdk/storage/storage-file-datalake/review/storage-file-datalake.api.md index ce14785ab425..aae1d8979508 100644 --- a/sdk/storage/storage-file-datalake/review/storage-file-datalake.api.md +++ b/sdk/storage/storage-file-datalake/review/storage-file-datalake.api.md @@ -152,6 +152,13 @@ export abstract class CredentialPolicy extends BaseRequestPolicy { // @public export type CredentialPolicyCreator = (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => CredentialPolicy; +// @public +export class DataLakeAclChangeFailedError extends Error { + constructor(error: RestError | Error, continuationToken?: string); + continuationToken?: string; + internalError: RestError | Error; +} + // @public export class DataLakeDirectoryClient extends DataLakePathClient { create(resourceType: PathResourceTypeModel, options?: PathCreateOptions): Promise; diff --git a/sdk/storage/storage-file-datalake/src/clients.ts b/sdk/storage/storage-file-datalake/src/clients.ts index 883e8ab19e8c..3dca03ba8938 100644 --- a/sdk/storage/storage-file-datalake/src/clients.ts +++ b/sdk/storage/storage-file-datalake/src/clients.ts @@ -5,6 +5,7 @@ import { BlobClient, BlockBlobClient } from "@azure/storage-blob"; import { CanonicalCode } from "@opentelemetry/api"; import { Readable } from "stream"; +import { BufferScheduler } from "../../storage-common/src"; import { AnonymousCredential } from "./credentials/AnonymousCredential"; import { StorageSharedKeyCredential } from "./credentials/StorageSharedKeyCredential"; import { DataLakeLeaseClient } from "./DataLakeLeaseClient"; @@ -21,12 +22,16 @@ import { FileCreateIfNotExistsResponse, FileCreateOptions, FileCreateResponse, + FileExpiryMode, FileFlushOptions, FileFlushResponse, FileParallelUploadOptions, + FileQueryOptions, FileReadOptions, FileReadResponse, FileReadToBufferOptions, + FileSetExpiryOptions, + FileSetExpiryResponse, FileUploadResponse, Metadata, PathAccessControlItem, @@ -57,11 +62,7 @@ import { PathSetMetadataResponse, PathSetPermissionsOptions, PathSetPermissionsResponse, - RemovePathAccessControlItem, - FileQueryOptions, - FileExpiryMode, - FileSetExpiryOptions, - FileSetExpiryResponse + RemovePathAccessControlItem } from "./models"; import { PathSetAccessControlRecursiveMode } from "./models.internal"; import { newPipeline, Pipeline, StoragePipelineOptions } from "./Pipeline"; @@ -74,7 +75,6 @@ import { toProperties } from "./transforms"; import { Batch } from "./utils/Batch"; - import { BLOCK_BLOB_MAX_BLOCKS, DEFAULT_HIGH_LEVEL_CONCURRENCY, @@ -84,10 +84,10 @@ import { FILE_UPLOAD_DEFAULT_CHUNK_SIZE, FILE_UPLOAD_MAX_CHUNK_SIZE } from "./utils/constants"; +import { DataLakeAclChangeFailedError } from "./utils/DataLakeAclChangeFailedError"; import { createSpan } from "./utils/tracing"; import { appendToURLPath, setURLPath } from "./utils/utils.common"; -import { fsStat, fsCreateReadStream } from "./utils/utils.node"; -import { BufferScheduler } from "../../storage-common/src"; +import { fsCreateReadStream, fsStat } from "./utils/utils.node"; /** * A DataLakePathClient represents a URL to the Azure Storage path (directory or file). @@ -159,14 +159,20 @@ export class DataLakePathClient extends StorageClient { let batchCounter = 0; let reachMaxBatches = false; do { - const response = await this.pathContext.setAccessControlRecursive(mode, { - ...options, - acl: toAclString(acl as PathAccessControlItem[]), - maxRecords: options.batchSize, - continuation: continuationToken, - forceFlag: options.continueOnFailure, - spanOptions - }); + let response; + try { + response = await this.pathContext.setAccessControlRecursive(mode, { + ...options, + acl: toAclString(acl as PathAccessControlItem[]), + maxRecords: options.batchSize, + continuation: continuationToken, + forceFlag: options.continueOnFailure, + spanOptions + }); + } catch (e) { + throw new DataLakeAclChangeFailedError(e, continuationToken); + } + batchCounter++; continuationToken = response.continuation; diff --git a/sdk/storage/storage-file-datalake/src/index.browser.ts b/sdk/storage/storage-file-datalake/src/index.browser.ts index 84fd3c410ff6..72359067aeaa 100644 --- a/sdk/storage/storage-file-datalake/src/index.browser.ts +++ b/sdk/storage/storage-file-datalake/src/index.browser.ts @@ -13,6 +13,7 @@ export * from "./policies/AnonymousCredentialPolicy"; export * from "./policies/CredentialPolicy"; export * from "./StorageRetryPolicyFactory"; export * from "./models"; +export * from "./utils/DataLakeAclChangeFailedError"; export { CommonOptions } from "./StorageClient"; export { ToBlobEndpointHostMappings, ToDfsEndpointHostMappings } from "./utils/constants"; export { RestError } from "@azure/core-http"; diff --git a/sdk/storage/storage-file-datalake/src/index.ts b/sdk/storage/storage-file-datalake/src/index.ts index 07ebfc43e6d4..a383df6f001c 100644 --- a/sdk/storage/storage-file-datalake/src/index.ts +++ b/sdk/storage/storage-file-datalake/src/index.ts @@ -23,6 +23,7 @@ export * from "./StorageRetryPolicyFactory"; export * from "./policies/StorageSharedKeyCredentialPolicy"; export * from "./sas/SASQueryParameters"; export * from "./models"; +export * from "./utils/DataLakeAclChangeFailedError"; export { CommonOptions } from "./StorageClient"; export { SasIPRange } from "./sas/SasIPRange"; export { ToBlobEndpointHostMappings, ToDfsEndpointHostMappings } from "./utils/constants"; diff --git a/sdk/storage/storage-file-datalake/src/utils/DataLakeAclChangeFailedError.ts b/sdk/storage/storage-file-datalake/src/utils/DataLakeAclChangeFailedError.ts new file mode 100644 index 000000000000..b0312e562cf2 --- /dev/null +++ b/sdk/storage/storage-file-datalake/src/utils/DataLakeAclChangeFailedError.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { RestError } from "@azure/core-http"; + +/** + * An error thrown when an operation is interrupted and can be continued later on. + * + * @export + * @class DataLakeAclChangeFailedError + * @extends {Error} + */ +export class DataLakeAclChangeFailedError extends Error { + /** + * Continuation token to continue next batch of operations. + * + * @type {string} + * @memberof DataLakeAclChangeFailedError + */ + public continuationToken?: string; + + /** + * Internal error. + * + * @type {(RestError | Error)} + * @memberof DataLakeAclChangeFailedError + */ + public internalError: RestError | Error; + + constructor(error: RestError | Error, continuationToken?: string) { + super(error.message); + this.name = "DataLakeAclChangeFailedError"; + this.internalError = error; + this.continuationToken = continuationToken; + Object.setPrototypeOf(this, DataLakeAclChangeFailedError.prototype); + } +}