From 741699876b2742f4d65b28839529aba42deb0762 Mon Sep 17 00:00:00 2001 From: "FAREAST\\jiacfan" Date: Wed, 4 Sep 2019 21:52:32 +0800 Subject: [PATCH] [Storage](Blob) Add overloads to blob batch addSubRequest methods which accept BlobURL as parameter. --- sdk/storage/storage-blob/src/BatchRequest.ts | 118 ++++++++++++++++-- sdk/storage/storage-blob/src/ServiceURL.ts | 8 +- sdk/storage/storage-blob/src/StorageURL.ts | 16 +++ .../storage-blob/test/blobbatch.spec.ts | 76 +++++++---- 4 files changed, 180 insertions(+), 38 deletions(-) diff --git a/sdk/storage/storage-blob/src/BatchRequest.ts b/sdk/storage/storage-blob/src/BatchRequest.ts index 89e013b2094c..b295c3628912 100644 --- a/sdk/storage/storage-blob/src/BatchRequest.ts +++ b/sdk/storage/storage-blob/src/BatchRequest.ts @@ -74,7 +74,7 @@ export abstract class BatchRequest { return this.batchRequest.getSubRequests(); } - protected async addSubRequest(subRequest: BatchSubRequest, assembleSubRequestFunc: ()=>Promise): Promise { + protected async addSubRequestInternal(subRequest: BatchSubRequest, assembleSubRequestFunc: ()=>Promise): Promise { await Mutex.lock(this.batch); try { @@ -108,16 +108,59 @@ export class BatchDeleteRequest extends BatchRequest { * * @param {string} url The url of the blob resource to delete. * @param {Credential} credential The credential to be used for authentication and authorization. - * @param {IBlobDeleteOptions} [options={}] + * @param {IBlobDeleteOptions} [options] * @returns {Promise} * @memberof BatchDeleteRequest */ - public async addDeleteOperation( + public async addSubRequest( url: string, credential: Credential, - options: IBlobDeleteOptions = {} - ): Promise { - await super.addSubRequest( + options?: IBlobDeleteOptions + ): Promise; + + /** + * Add a delete operation(subrequest) to mark the specified blob or snapshot for deletion. + * Note that in order to delete a blob, you must delete all of its snapshots. + * You can delete both at the same time. See [delete operation details](https://docs.microsoft.com/en-us/rest/api/storageservices/delete-blob). + * The operation(subrequest) will be authenticated and authorized with specified credential. + * See [blob batch authorization details](https://docs.microsoft.com/en-us/rest/api/storageservices/blob-batch#authorization). + * + * @param {blobUrl} BlobURL The BlobURL. + * @param {IBlobDeleteOptions} [options] + * @returns {Promise} + * @memberof BatchDeleteRequest + */ + public async addSubRequest( + blobURL: BlobURL, + options?: IBlobDeleteOptions + ): Promise; + + public async addSubRequest( + urlOrBlobURL: string | BlobURL, + credentialOrOptions: Credential | IBlobDeleteOptions | undefined, + options?: IBlobDeleteOptions + ): Promise{ + let url: string; + let credential: Credential; + + if (typeof urlOrBlobURL === 'string' && credentialOrOptions instanceof Credential) { + // First overload + url = urlOrBlobURL; + credential = credentialOrOptions; + } else if (urlOrBlobURL instanceof BlobURL) { + // Second overload + url = urlOrBlobURL.url; + credential = urlOrBlobURL.credential; + options = credentialOrOptions as IBlobDeleteOptions; + } else { + throw new RangeError("Invalid arguments. Either url and credential, or BlobURL need be provided.") + } + + if (!options) { + options = {}; + } + + await super.addSubRequestInternal( { url: url, credential: credential @@ -158,17 +201,70 @@ export class BatchSetTierRequest extends BatchRequest { * @param {string} url The url of the blob resource to delete. * @param {Credential} credential The credential to be used for authentication and authorization. * @param {Models.AccessTier} tier - * @param {IBlobSetTierOptions} [options={}] + * @param {IBlobSetTierOptions} [options] * @returns {Promise} * @memberof BatchSetTierRequest */ - public async addSetTierOperation( + public async addSubRequest( url: string, credential: Credential, tier: Models.AccessTier, - options: IBlobSetTierOptions = {} - ): Promise { - await super.addSubRequest( + options?: IBlobSetTierOptions + ): Promise; + + /** + * Add a set tier operation(subrequest) to set the tier on a blob. + * The operation is allowed on a page blob in a premium + * storage account and on a block blob in a blob storage account (locally redundant + * storage only). A premium page blob's tier determines the allowed size, IOPS, + * and bandwidth of the blob. A block blob's tier determines Hot/Cool/Archive + * storage type. This operation does not update the blob's ETag. + * See [set blob tier details](https://docs.microsoft.com/en-us/rest/api/storageservices/set-blob-tier). + * The operation(subrequest) will be authenticated and authorized + * with specified credential.See [blob batch authorization details](https://docs.microsoft.com/en-us/rest/api/storageservices/blob-batch#authorization). + * + * @param {blobUrl} BlobURL The BlobURL. + * @param {Models.AccessTier} tier + * @param {IBlobSetTierOptions} [options] + * @returns {Promise} + * @memberof BatchSetTierRequest + */ + public async addSubRequest( + blobURL: BlobURL, + tier: Models.AccessTier, + options?: IBlobSetTierOptions + ): Promise; + + public async addSubRequest( + urlOrBlobURL: string | BlobURL, + credentialOrTier: Credential | Models.AccessTier, + tierOrOptions?: Models.AccessTier | IBlobSetTierOptions, + options?: IBlobSetTierOptions + ): Promise{ + let url: string; + let credential: Credential; + let tier: Models.AccessTier; + + if (typeof urlOrBlobURL === 'string' && credentialOrTier instanceof Credential) { + // First overload + url = urlOrBlobURL; + credential = credentialOrTier as Credential; + tier = tierOrOptions as Models.AccessTier; + } else if (urlOrBlobURL instanceof BlobURL) { + // Second overload + url = urlOrBlobURL.url; + credential = urlOrBlobURL.credential; + tier = credentialOrTier as Models.AccessTier; + options = tierOrOptions as IBlobSetTierOptions; + } else { + throw new RangeError("Invalid arguments. Either url and credential, or BlobURL need be provided.") + } + + if (!options) { + options = {}; + } + + await super.addSubRequestInternal( { url: url, credential: credential diff --git a/sdk/storage/storage-blob/src/ServiceURL.ts b/sdk/storage/storage-blob/src/ServiceURL.ts index e8485e27ec6d..92a1625bdb66 100644 --- a/sdk/storage/storage-blob/src/ServiceURL.ts +++ b/sdk/storage/storage-blob/src/ServiceURL.ts @@ -327,8 +327,8 @@ export class ServiceURL extends StorageURL { * * @example * let batchDeleteRequest = new BatchDeleteRequest(); - * await batchDeleteRequest.addDeleteOperation(blockBlobURL0, credential); - * await batchDeleteRequest.addDeleteOperation(blockBlobURL1, credential, { + * await batchDeleteRequest.addSubRequest(urlInString0, credential0); + * await batchDeleteRequest.addSubRequest(urlInString1, credential1, { * deleteSnapshots: "include" * }); * const deleteBatchResp = await serviceURL.submitBatch(Aborter.none, batchDeleteRequest); @@ -336,8 +336,8 @@ export class ServiceURL extends StorageURL { * * @example * let batchSetTierRequest = new BatchSetTierRequest(); - * await batchSetTierRequest.addSetTierOperation(blockBlobURL0, credential0, "Cool"); - * await batchSetTierRequest.addSetTierOperation(blockBlobURL1, credential1, "Cool", { + * await batchSetTierRequest.addSubRequest(blockBlobURL0, "Cool"); + * await batchSetTierRequest.addSubRequest(blockBlobURL1, "Cool", { * leaseAccessConditions: { leaseId: leaseId } * }); * const setTierBatchResp = await serviceURL.submitBatch(Aborter.none, batchSetTierRequest); diff --git a/sdk/storage/storage-blob/src/StorageURL.ts b/sdk/storage/storage-blob/src/StorageURL.ts index 658b679b55a6..21a68caeeb70 100644 --- a/sdk/storage/storage-blob/src/StorageURL.ts +++ b/sdk/storage/storage-blob/src/StorageURL.ts @@ -10,6 +10,7 @@ import { IRetryOptions, RetryPolicyFactory } from "./RetryPolicyFactory"; import { ITelemetryOptions, TelemetryPolicyFactory } from "./TelemetryPolicyFactory"; import { UniqueRequestIDPolicyFactory } from "./UniqueRequestIDPolicyFactory"; import { escapeURLPath, getURLScheme, iEqual } from "./utils/utils.common"; +import { AnonymousCredential } from './credentials/AnonymousCredential'; export { deserializationPolicy }; @@ -105,6 +106,14 @@ export abstract class StorageURL { */ public readonly url: string; + /** + * Credential used for authentication and authorization. + * + * @type {string} + * @memberof StorageURL + */ + public readonly credential: Credential; + /** * StorageClient is a reference to protocol layer operations entry, which is * generated by AutoRest generator. @@ -139,6 +148,13 @@ export abstract class StorageURL { this.isHttps = iEqual(getURLScheme(this.url) || "", "https"); + this.credential = new AnonymousCredential(); + for (const factory of this.pipeline.factories) { + if (factory instanceof Credential) { + this.credential = factory; + } + } + // Override protocol layer's default content-type const storageClientContext = this.storageClientContext as any; storageClientContext.requestContentType = undefined; diff --git a/sdk/storage/storage-blob/test/blobbatch.spec.ts b/sdk/storage/storage-blob/test/blobbatch.spec.ts index cb1cac775baa..15d869644c75 100644 --- a/sdk/storage/storage-blob/test/blobbatch.spec.ts +++ b/sdk/storage/storage-blob/test/blobbatch.spec.ts @@ -57,7 +57,7 @@ describe("BlobURL", () => { // Assemble batch delete request. let batchDeleteRequest = new BatchDeleteRequest(); for (let i = 0; i < blockBlobCount; i++) { - await batchDeleteRequest.addDeleteOperation(blockBlobURLs[i].url, credential, {}); + await batchDeleteRequest.addSubRequest(blockBlobURLs[i].url, credential, {}); } // Submit batch request and verify response. @@ -91,12 +91,12 @@ describe("BlobURL", () => { // Assemble batch delete request which delete blob with its snapshot. let batchDeleteRequest = new BatchDeleteRequest(); - await batchDeleteRequest.addDeleteOperation(blockBlobURLs[0].url, credential, { + await batchDeleteRequest.addSubRequest(blockBlobURLs[0].url, credential, { deleteSnapshots: "include" }); // Ensure blobs ready. - const respList1 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + let respList1 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { include: ["snapshots"] }); assert.equal(respList1.segment.blobItems.length, 2); @@ -108,13 +108,13 @@ describe("BlobURL", () => { assert.equal(respSubmitBatch1.subResponsesFailedCount, 0); // Validate that blob and its snapshot all get deleted. - const respList2 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + respList1 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { include: ["snapshots"] }); - assert.equal(respList2.segment.blobItems.length, 0); + assert.equal(respList1.segment.blobItems.length, 0); // - // Test delete snapshot only with snapshot's url + // Test delete snapshot only with snapshot's url and Credential. // // Upload blob. await blockBlobURLs[1].upload(Aborter.none, content, content.length); @@ -123,13 +123,13 @@ describe("BlobURL", () => { // Assemble batch delete request. let batchDeleteRequest2 = new BatchDeleteRequest(); - await batchDeleteRequest2.addDeleteOperation(snapshotURL.url, credential); + await batchDeleteRequest2.addSubRequest(snapshotURL.url, credential); // Ensure blobs ready. - const respList3 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + let respList2 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { include: ["snapshots"] }); - assert.equal(respList3.segment.blobItems.length, 2); + assert.equal(respList2.segment.blobItems.length, 2); // Submit batch request and verify response. const respSubmitBatch2 = await serviceURL.submitBatch(Aborter.none, batchDeleteRequest2, {}); @@ -137,11 +137,41 @@ describe("BlobURL", () => { assert.equal(respSubmitBatch2.subResponsesSucceededCount, 1); assert.equal(respSubmitBatch2.subResponsesFailedCount, 0); - // Validate that blob and its snapshot all get deleted. - const respList4 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + // Validate that snapshot get deleted. + respList2 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + include: ["snapshots"] + }); + assert.equal(respList2.segment.blobItems.length, 1); + + // + // Test delete snapshot only with snapshot's url using snapshot's BlobURL. + // + // Upload blob. + await blockBlobURLs[2].upload(Aborter.none, content, content.length); + const createSnapshotResp2 = await blockBlobURLs[2].createSnapshot(Aborter.none); + const snapshotURL2 = blockBlobURLs[2].withSnapshot(createSnapshotResp2.snapshot!); + + // Assemble batch delete request. + let batchDeleteRequest3 = new BatchDeleteRequest(); + await batchDeleteRequest3.addSubRequest(snapshotURL2); + + // Ensure blobs ready. + let respList3 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { include: ["snapshots"] }); - assert.equal(respList4.segment.blobItems.length, 1); + assert.equal(respList3.segment.blobItems.length, 3); + + // Submit batch request and verify response. + const respSubmitBatch3 = await serviceURL.submitBatch(Aborter.none, batchDeleteRequest3, {}); + assert.equal(respSubmitBatch3.subResponses.length, 1); + assert.equal(respSubmitBatch3.subResponsesSucceededCount, 1); + assert.equal(respSubmitBatch3.subResponsesFailedCount, 0); + + // Validate that snapshot get deleted. + respList3 = await containerURL.listBlobFlatSegment(Aborter.none, undefined, { + include: ["snapshots"] + }); + assert.equal(respList3.segment.blobItems.length, 2); }); it("submitBatch should work for batch delete with access condition and partial succeed", async () => { @@ -151,14 +181,14 @@ describe("BlobURL", () => { // Assemble batch delete request. let batchDeleteRequest = new BatchDeleteRequest(); - await batchDeleteRequest.addDeleteOperation(blockBlobURLs[0].url, credential, { + await batchDeleteRequest.addSubRequest(blockBlobURLs[0], { blobAccessConditions: { modifiedAccessConditions: { ifMatch: b0.eTag } } }); - await batchDeleteRequest.addDeleteOperation(blockBlobURLs[1].url, credential, { + await batchDeleteRequest.addSubRequest(blockBlobURLs[1], { blobAccessConditions: { modifiedAccessConditions: { ifNoneMatch: b1.eTag @@ -194,7 +224,7 @@ describe("BlobURL", () => { // Assemble batch set tier request. let batchSetTierRequest = new BatchSetTierRequest(); for (let i = 0; i < blockBlobCount; i++) { - await batchSetTierRequest.addSetTierOperation(blockBlobURLs[i].url, credential, "Cool", {}); + await batchSetTierRequest.addSubRequest(blockBlobURLs[i].url, credential, "Cool", {}); } // Submit batch request and verify response. @@ -229,8 +259,8 @@ describe("BlobURL", () => { // Assemble batch set tier request. let batchSetTierRequest = new BatchSetTierRequest(); - await batchSetTierRequest.addSetTierOperation(blockBlobURLs[0].url, credential, "Cool"); - await batchSetTierRequest.addSetTierOperation(blockBlobURLs[1].url, credential, "Cool", { + await batchSetTierRequest.addSubRequest(blockBlobURLs[0], "Cool"); + await batchSetTierRequest.addSubRequest(blockBlobURLs[1], "Cool", { leaseAccessConditions: { leaseId: leaseResp.leaseId! } }); @@ -260,13 +290,13 @@ describe("BlobURL", () => { // Assemble batch set tier request. let batchSetTierRequest = new BatchSetTierRequest(); - await batchSetTierRequest.addSetTierOperation(blockBlobURLs[0].url, credential, "Cool"); + await batchSetTierRequest.addSubRequest(blockBlobURLs[0].url, credential, "Cool"); // When it's using token credential be sure it's not with SAS (browser testing case) let blockBlobURL1WithoutSAS = blockBlobURLs[1].url; if (blockBlobURL1WithoutSAS.indexOf("?") != -1) { // remove query part for this testing for ease blockBlobURL1WithoutSAS = blockBlobURLs[1].url.substring(0, blockBlobURLs[1].url.indexOf("?")); } - await batchSetTierRequest.addSetTierOperation(blockBlobURL1WithoutSAS, getTokenCredential(), "Cool"); + await batchSetTierRequest.addSubRequest(blockBlobURL1WithoutSAS, getTokenCredential(), "Cool"); // Submit batch request and verify response. const resp = await serviceURL.submitBatch(Aborter.none, batchSetTierRequest, {}); @@ -295,14 +325,14 @@ describe("BlobURL", () => { for (let i = 0; i < 256; i++) { let tmpBlobURL = BlobURL.fromContainerURL(containerURL, `blob${i}`); - await batchSetTierRequest.addSetTierOperation(tmpBlobURL.url, credential, "Cool"); + await batchSetTierRequest.addSubRequest(tmpBlobURL.url, credential, "Cool"); } let exceptionCaught = false; try { let tmpBlobURL = BlobURL.fromContainerURL(containerURL, `blobexceed`); - await batchSetTierRequest.addSetTierOperation(tmpBlobURL.url, credential, "Cool"); + await batchSetTierRequest.addSubRequest(tmpBlobURL.url, credential, "Cool"); } catch (err) { if ( err instanceof RangeError && @@ -320,7 +350,7 @@ describe("BlobURL", () => { let exceptionCaught = false; try { - await batchSetTierRequest.addSetTierOperation("invalidurl", credential, "Cool"); + await batchSetTierRequest.addSubRequest("invalidurl", credential, "Cool"); } catch (err) { if ( err instanceof RangeError && @@ -355,7 +385,7 @@ describe("BlobURL", () => { // Assemble batch set tier request. let batchSetTierRequest = new BatchSetTierRequest(); - await batchSetTierRequest.addSetTierOperation(blockBlobURLs[0].url, credential, "Cool"); + await batchSetTierRequest.addSubRequest(blockBlobURLs[0].url, credential, "Cool"); const invalidCredServiceURL = serviceURL.withPipeline(StorageURL.newPipeline(new TokenCredential("invalidtoken")))