Skip to content

Commit

Permalink
[Storage](Blob) Add overloads to blob batch addSubRequest methods whi…
Browse files Browse the repository at this point in the history
…ch accept BlobURL as parameter.
  • Loading branch information
jiacfan authored and vinjiang committed Sep 9, 2019
1 parent 21a9b66 commit 7416998
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 38 deletions.
118 changes: 107 additions & 11 deletions sdk/storage/storage-blob/src/BatchRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export abstract class BatchRequest {
return this.batchRequest.getSubRequests();
}

protected async addSubRequest(subRequest: BatchSubRequest, assembleSubRequestFunc: ()=>Promise<void>): Promise<void> {
protected async addSubRequestInternal(subRequest: BatchSubRequest, assembleSubRequestFunc: ()=>Promise<void>): Promise<void> {
await Mutex.lock(this.batch);

try {
Expand Down Expand Up @@ -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<void>}
* @memberof BatchDeleteRequest
*/
public async addDeleteOperation(
public async addSubRequest(
url: string,
credential: Credential,
options: IBlobDeleteOptions = {}
): Promise<void> {
await super.addSubRequest(
options?: IBlobDeleteOptions
): Promise<void>;

/**
* 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<void>}
* @memberof BatchDeleteRequest
*/
public async addSubRequest(
blobURL: BlobURL,
options?: IBlobDeleteOptions
): Promise<void>;

public async addSubRequest(
urlOrBlobURL: string | BlobURL,
credentialOrOptions: Credential | IBlobDeleteOptions | undefined,
options?: IBlobDeleteOptions
): Promise<void>{
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
Expand Down Expand Up @@ -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<void>}
* @memberof BatchSetTierRequest
*/
public async addSetTierOperation(
public async addSubRequest(
url: string,
credential: Credential,
tier: Models.AccessTier,
options: IBlobSetTierOptions = {}
): Promise<void> {
await super.addSubRequest(
options?: IBlobSetTierOptions
): Promise<void>;

/**
* 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<void>}
* @memberof BatchSetTierRequest
*/
public async addSubRequest(
blobURL: BlobURL,
tier: Models.AccessTier,
options?: IBlobSetTierOptions
): Promise<void>;

public async addSubRequest(
urlOrBlobURL: string | BlobURL,
credentialOrTier: Credential | Models.AccessTier,
tierOrOptions?: Models.AccessTier | IBlobSetTierOptions,
options?: IBlobSetTierOptions
): Promise<void>{
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
Expand Down
8 changes: 4 additions & 4 deletions sdk/storage/storage-blob/src/ServiceURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,17 +327,17 @@ 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);
* console.log(deleteBatchResp.subResponsesSucceededCount);
*
* @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);
Expand Down
16 changes: 16 additions & 0 deletions sdk/storage/storage-blob/src/StorageURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
76 changes: 53 additions & 23 deletions sdk/storage/storage-blob/test/blobbatch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -123,25 +123,55 @@ 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, {});
assert.equal(respSubmitBatch2.subResponses.length, 1);
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 () => {
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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! }
});

Expand Down Expand Up @@ -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, {});
Expand Down Expand Up @@ -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 &&
Expand All @@ -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 &&
Expand Down Expand Up @@ -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")))

Expand Down

0 comments on commit 7416998

Please sign in to comment.