diff --git a/.gitignore b/.gitignore index c3f59e447..a6832ed58 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ temp azurite.exe .pkg-cache release -querydb*.json \ No newline at end of file +querydb*.json +.idea diff --git a/ChangeLog.md b/ChangeLog.md index a96f0f198..35e670759 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,7 @@ Blob: - Fixed startCopyFromURL, copyFromURL API to return 400 (InvalidHeaderValue) when copy source has invalid format. (issue #1954) - Fixed CommitBlockList API to return 400 (InvalidXmlDocument) when the request is sent with JSON body. (issue #1955) - Added "x-ms-is-hns-enabled" header in x-ms-is-hns-enabled API responds (issue #1810) +- Blob Copy & Page Blob are now supported by SQL based metadata implementation. This making the SQL based metadata implementation in sync with File based one. (issue #2224) Queue: diff --git a/README.md b/README.md index 8db16d5ba..d414f2c99 100644 --- a/README.md +++ b/README.md @@ -488,8 +488,6 @@ This feature is in preview, when Azurite changes database table schema, you need > Note. Need to manually create database before starting Azurite instance. -> Note. Blob Copy & Page Blob are not supported by SQL based metadata implementation. - > Tips. Create database instance quickly with docker, for example `docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:latest`. Grant external access and create database `azurite_blob` using `docker exec mysql mysql -u root -pmy-secret-pw -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES; create database azurite_blob;"`. Notice that, above commands are examples, you need to carefully define the access permissions in your production environment. ## HTTPS Setup diff --git a/src/blob/persistence/SqlBlobMetadataStore.ts b/src/blob/persistence/SqlBlobMetadataStore.ts index ad0d96264..91a1a4e59 100644 --- a/src/blob/persistence/SqlBlobMetadataStore.ts +++ b/src/blob/persistence/SqlBlobMetadataStore.ts @@ -20,7 +20,7 @@ import { import { convertDateTimeStringMsTo7Digital } from "../../common/utils/utils"; import { newEtag } from "../../common/utils/utils"; import { validateReadConditions } from "../conditions/ReadConditionalHeadersValidator"; -import { validateWriteConditions } from "../conditions/WriteConditionalHeadersValidator"; +import { validateSequenceNumberWriteConditions, validateWriteConditions } from "../conditions/WriteConditionalHeadersValidator"; import StorageErrorFactory from "../errors/StorageErrorFactory"; import * as Models from "../generated/artifacts/models"; import Context from "../generated/Context"; @@ -37,7 +37,8 @@ import { ILease } from "../lease/ILeaseState"; import LeaseFactory from "../lease/LeaseFactory"; import { DEFAULT_LIST_BLOBS_MAX_RESULTS, - DEFAULT_LIST_CONTAINERS_MAX_RESULTS + DEFAULT_LIST_CONTAINERS_MAX_RESULTS, + MAX_APPEND_BLOB_BLOCK_COUNT } from "../utils/constants"; import BlobReferredExtentsAsyncIterator from "./BlobReferredExtentsAsyncIterator"; import IBlobMetadataStore, { @@ -67,6 +68,7 @@ import IBlobMetadataStore, { SetContainerAccessPolicyOptions } from "./IBlobMetadataStore"; import PageWithDelimiter from "./PageWithDelimiter"; +import PageBlobRangesManager from "../handlers/PageBlobRangesManager"; import { getBlobTagsCount, getTagsFromString } from "../utils/utils"; // tslint:disable: max-classes-per-file @@ -76,16 +78,6 @@ class BlobsModel extends Model {} class BlocksModel extends Model {} // class PagesModel extends Model {} -interface IBlobContentProperties { - contentLength?: number; - contentType?: string; - contentEncoding?: string; - contentLanguage?: string; - contentMD5?: Uint8Array; - contentDisposition?: string; - cacheControl?: string; -} - /** * A SQL based Blob metadata storage implementation based on Sequelize. * Refer to CONTRIBUTION.md for how to setup SQL database environment. @@ -98,6 +90,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { private initialized: boolean = false; private closed: boolean = false; private readonly sequelize: Sequelize; + private readonly pageBlobRangesManager = new PageBlobRangesManager(); /** * Creates an instance of SqlBlobMetadataStore. @@ -188,6 +181,10 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { metadata: { type: "VARCHAR(4095)" }, + properties: { + allowNull: true, + type: "VARCHAR(4095)" + }, containerAcl: { type: "VARCHAR(1023)" }, @@ -238,37 +235,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { primaryKey: true, autoIncrement: true }, - lastModified: { - allowNull: false, - type: DATE(6) - }, - creationTime: { - allowNull: false, - type: DATE(6) - }, - accessTierChangeTime: { - allowNull: true, - type: DATE(6) - }, - accessTierInferred: { - type: BOOLEAN - }, - etag: { - allowNull: false, - type: "VARCHAR(127)" - }, - blobType: { - allowNull: false, - type: "VARCHAR(31)" - }, - blobSequenceNumber: { - type: "VARCHAR(63)" - }, - accessTier: { - type: "VARCHAR(31)" - }, - contentProperties: { - type: "VARCHAR(1023)" + properties: { + type: "VARCHAR(4095)" }, lease: { type: "VARCHAR(1023)" @@ -288,6 +256,9 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { committedBlocksInOrder: { type: TEXT({ length: "medium" }) }, + pageRangesInOrder: { + type: TEXT({ length: "medium" }) + }, metadata: { type: "VARCHAR(2047)" }, @@ -392,7 +363,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context: Context, serviceProperties: ServicePropertiesModel ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { const findResult = await ServicesModel.findByPk( serviceProperties.accountName, { @@ -683,7 +654,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { /* Transaction starts */ const findResult = await ContainersModel.findOne({ attributes: [ @@ -832,7 +803,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { container: string, options: Models.ContainerAcquireLeaseOptionalParams ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { /* Transaction starts */ const findResult = await ContainersModel.findOne({ where: { @@ -886,7 +857,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseId: string, options: Models.ContainerReleaseLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { /* Transaction starts */ const findResult = await ContainersModel.findOne({ where: { @@ -938,7 +909,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseId: string, options: Models.ContainerRenewLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { /* Transaction starts */ // TODO: Filter out unnecessary fields in select query const findResult = await ContainersModel.findOne({ @@ -994,7 +965,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { breakPeriod: number | undefined, options: Models.ContainerBreakLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { const findResult = await ContainersModel.findOne({ where: { accountName: account, @@ -1058,7 +1029,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { proposedLeaseId: string, options: Models.ContainerChangeLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { const findResult = await ContainersModel.findOne({ where: { accountName: account, @@ -1118,7 +1089,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists( context, blob.accountName, @@ -1187,7 +1158,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -1239,7 +1210,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { includeSnapshots?: boolean, includeUncommittedBlobs?: boolean ): Promise<[BlobModel[], BlobPrefixModel[], any | undefined]> { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const whereQuery: any = { @@ -1439,7 +1410,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { }); } - public getBlockList( + public async getBlockList( context: Context, account: string, container: string, @@ -1448,7 +1419,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { isCommitted?: boolean, leaseAccessConditions?: Models.LeaseAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -1692,7 +1663,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -1727,8 +1698,6 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { throw StorageErrorFactory.getBlobNotFound(context.contextId); } - // TODO: Return blobCommittedBlockCount for append blob - let responds = LeaseFactory.createLeaseState( new BlobLeaseAdapter(blobModel), context @@ -1740,6 +1709,10 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { properties : { ...responds.properties, tagCount: getBlobTagsCount(blobModel.blobTags), + blobCommittedBlockCount: + responds.properties.blobType === Models.BlobType.AppendBlob + ? (responds.committedBlocksInOrder || []).length + : undefined }, } }); @@ -1758,7 +1731,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { metadata?: Models.BlobMetadata, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2005,7 +1978,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { blobHTTPHeaders: Models.BlobHTTPHeaders | undefined, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2072,7 +2045,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { }); } - public setBlobMetadata( + public async setBlobMetadata( context: Context, account: string, container: string, @@ -2081,7 +2054,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { metadata: Models.BlobMetadata | undefined, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2117,13 +2090,10 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { const lastModified = context.startTime! || new Date(); const etag = newEtag(); - await BlobsModel.update( - { - metadata: this.serializeModelValue(metadata) || null, - lastModified, - etag, - ...this.convertLeaseToDbModel(new BlobLeaseAdapter(blobModel)) - }, + blobModel.metadata = metadata; + blobModel.properties.lastModified = lastModified; + + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, @@ -2157,7 +2127,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { proposedLeaseId?: string, options: Models.BlobAcquireLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2191,7 +2161,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context ).acquire(duration, proposedLeaseId).lease; - await BlobsModel.update(this.convertLeaseToDbModel(lease), { + new BlobLeaseSyncer(blobModel).sync(lease); + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, containerName: container, @@ -2213,7 +2184,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseId: string, options: Models.BlobReleaseLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2247,7 +2218,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context ).release(leaseId).lease; - await BlobsModel.update(this.convertLeaseToDbModel(lease), { + new BlobLeaseSyncer(blobModel).sync(lease); + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, containerName: container, @@ -2269,7 +2241,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseId: string, options: Models.BlobRenewLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2303,7 +2275,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context ).renew(leaseId).lease; - await BlobsModel.update(this.convertLeaseToDbModel(lease), { + new BlobLeaseSyncer(blobModel).sync(lease); + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, containerName: container, @@ -2326,7 +2299,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { proposedLeaseId: string, options: Models.BlobChangeLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2360,7 +2333,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context ).change(leaseId, proposedLeaseId).lease; - await BlobsModel.update(this.convertLeaseToDbModel(lease), { + new BlobLeaseSyncer(blobModel).sync(lease); + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, containerName: container, @@ -2382,7 +2356,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { breakPeriod: number | undefined, options: Models.BlobBreakLeaseOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2425,7 +2399,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { ) : 0; - await BlobsModel.update(this.convertLeaseToDbModel(lease), { + new BlobLeaseSyncer(blobModel).sync(lease); + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, containerName: container, @@ -2488,13 +2463,13 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { return undefined; } - const blobType = this.getModelValue(res, "blobType", true); + const blobType = this.getModelValue(res, "properties", true).blobType; const isCommitted = this.getModelValue(res, "isCommitted", true); return { blobType, isCommitted }; } - public startCopyFromURL( + public async startCopyFromURL( context: Context, source: BlobId, destination: BlobId, @@ -2503,7 +2478,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { tier: Models.AccessTier | undefined, options: Models.BlobStartCopyFromURLOptionalParams = {} ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { const sourceBlob = await this.getBlobWithLeaseUpdated( source.account, source.container, @@ -2547,6 +2522,15 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { destBlob ); + // Copy if not exists + if ( + options.modifiedAccessConditions && + options.modifiedAccessConditions.ifNoneMatch === "*" && + destBlob + ) { + throw StorageErrorFactory.getBlobAlreadyExists(context.contextId); + } + if (destBlob) { new BlobWriteLeaseValidator(options.leaseAccessConditions).validate( new BlobLeaseAdapter(destBlob), @@ -2662,17 +2646,193 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { }); } - public copyFromURL( + public async copyFromURL( context: Context, source: BlobId, destination: BlobId, copySource: string, - metadata: Models.BlobMetadata | undefined + metadata: Models.BlobMetadata | undefined, + tier: Models.AccessTier | undefined, + options: Models.BlobCopyFromURLOptionalParams = {} ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const sourceBlob = await this.getBlobWithLeaseUpdated( + source.account, + source.container, + source.blob, + source.snapshot, + context, + true, + true + ); + + options.sourceModifiedAccessConditions = + options.sourceModifiedAccessConditions || {}; + validateReadConditions( + context, + { + ifModifiedSince: + options.sourceModifiedAccessConditions.sourceIfModifiedSince, + ifUnmodifiedSince: + options.sourceModifiedAccessConditions.sourceIfUnmodifiedSince, + ifMatch: options.sourceModifiedAccessConditions.sourceIfMatch, + ifNoneMatch: options.sourceModifiedAccessConditions.sourceIfNoneMatch + }, + sourceBlob + ); + + const destBlob = await this.getBlobWithLeaseUpdated( + destination.account, + destination.container, + destination.blob, + undefined, + context, + false + ); + + validateWriteConditions( + context, + options.modifiedAccessConditions, + destBlob + ); + + // Copy if not exists + if ( + options.modifiedAccessConditions && + options.modifiedAccessConditions.ifNoneMatch === "*" && + destBlob + ) { + throw StorageErrorFactory.getBlobAlreadyExists(context.contextId); + } + + if (destBlob) { + const lease = new BlobLeaseAdapter(destBlob); + new BlobWriteLeaseSyncer(destBlob).sync(lease); + new BlobWriteLeaseValidator(options.leaseAccessConditions).validate( + lease, + context + ); + } + + // If source is uncommitted or deleted + if ( + sourceBlob === undefined || + sourceBlob.deleted || + !sourceBlob.isCommitted + ) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + if (sourceBlob.properties.accessTier === Models.AccessTier.Archive) { + throw StorageErrorFactory.getBlobArchived(context.contextId); + } + + await this.checkContainerExist( + context, + destination.account, + destination.container + ); + + // Deep clone a copied blob + const copiedBlob: BlobModel = { + name: destination.blob, + deleted: false, + snapshot: "", + properties: { + ...sourceBlob.properties, + creationTime: context.startTime!, + lastModified: context.startTime!, + etag: newEtag(), + leaseStatus: + destBlob !== undefined + ? destBlob.properties.leaseStatus + : Models.LeaseStatusType.Unlocked, + leaseState: + destBlob !== undefined + ? destBlob.properties.leaseState + : Models.LeaseStateType.Available, + leaseDuration: + destBlob !== undefined + ? destBlob.properties.leaseDuration + : undefined, + copyId: uuid(), + copyStatus: Models.CopyStatusType.Success, + copySource, + copyProgress: sourceBlob.properties.contentLength + ? `${sourceBlob.properties.contentLength}/${sourceBlob.properties.contentLength}` + : undefined, + copyCompletionTime: context.startTime, + copyStatusDescription: undefined, + incrementalCopy: false, + destinationSnapshot: undefined, + deletedTime: undefined, + remainingRetentionDays: undefined, + archiveStatus: undefined, + accessTierChangeTime: undefined + }, + metadata: + metadata === undefined || Object.keys(metadata).length === 0 + ? { ...sourceBlob.metadata } + : metadata, + accountName: destination.account, + containerName: destination.container, + pageRangesInOrder: sourceBlob.pageRangesInOrder, + isCommitted: sourceBlob.isCommitted, + leaseDurationSeconds: + destBlob !== undefined ? destBlob.leaseDurationSeconds : undefined, + leaseId: destBlob !== undefined ? destBlob.leaseId : undefined, + leaseExpireTime: + destBlob !== undefined ? destBlob.leaseExpireTime : undefined, + leaseBreakTime: + destBlob !== undefined ? destBlob.leaseBreakTime : undefined, + committedBlocksInOrder: sourceBlob.committedBlocksInOrder, + persistency: sourceBlob.persistency, + blobTags: options.blobTagsString === undefined ? undefined : getTagsFromString(options.blobTagsString, context.contextId!) + }; + + if ( + copiedBlob.properties.blobType === Models.BlobType.BlockBlob && + tier !== undefined + ) { + copiedBlob.properties.accessTier = this.parseTier(tier); + if (copiedBlob.properties.accessTier === undefined) { + throw StorageErrorFactory.getInvalidHeaderValue(context.contextId, { + HeaderName: "x-ms-access-tier", + HeaderValue: `${tier}` + }); + } + } + + if ( + copiedBlob.properties.blobType === Models.BlobType.PageBlob && + tier !== undefined + ) { + throw StorageErrorFactory.getInvalidHeaderValue(context.contextId, { + HeaderName: "x-ms-access-tier", + HeaderValue: `${tier}` + }); + } + + if (destBlob) { + await BlobsModel.destroy({ + where: { + accountName: destBlob.accountName, + containerName: destBlob.containerName, + blobName: destBlob.name + }, + transaction: t + }); + } + + await BlobsModel.upsert(this.convertBlobModelToDbModel(copiedBlob), { + transaction: t + }); + + return copiedBlob.properties; + }); } - public setTier( + public async setTier( context: Context, account: string, container: string, @@ -2680,7 +2840,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { tier: Models.AccessTier, leaseAccessConditions?: Models.LeaseAccessConditions ): Promise<200 | 202> { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -2748,12 +2908,11 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { context.contextId! ); } - await BlobsModel.update( - { - accessTier, - accessTierInferred: false, - accessTierChangeTime: context.startTime - }, + blobModel.properties.accessTier = accessTier; + blobModel.properties.accessTierInferred = false; + blobModel.properties.accessTierChangeTime = context.startTime; + + await BlobsModel.update(this.convertBlobModelToDbModel(blobModel), { where: { accountName: account, @@ -2770,30 +2929,118 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { }); } - public uploadPages( + public async uploadPages( context: Context, blob: BlobModel, start: number, end: number, persistency: IExtentChunk, leaseAccessConditions?: Models.LeaseAccessConditions, - modifiedAccessConditions?: Models.ModifiedAccessConditions + modifiedAccessConditions?: Models.ModifiedAccessConditions, + sequenceNumberAccessConditions?: Models.SequenceNumberAccessConditions ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const doc = await this.getBlobWithLeaseUpdated( + blob.accountName, + blob.containerName, + blob.name, + blob.snapshot, + context!, + false, + true, + t + ); + + validateWriteConditions(context, modifiedAccessConditions, doc); + + validateSequenceNumberWriteConditions( + context, + sequenceNumberAccessConditions, + doc + ); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + if (doc.properties.blobType !== Models.BlobType.PageBlob) { + throw StorageErrorFactory.getBlobInvalidBlobType(context.contextId); + } + + const lease = new BlobLeaseAdapter(doc); + new BlobWriteLeaseValidator(leaseAccessConditions).validate(lease, context); + + this.pageBlobRangesManager.mergeRange(doc.pageRangesInOrder || [], { + start, + end, + persistency + }); + + // set lease state to available if it's expired + new BlobWriteLeaseSyncer(doc).sync(lease); + + doc.properties.etag = newEtag(); + doc.properties.lastModified = context.startTime || new Date(); + + await BlobsModel.upsert(this.convertBlobModelToDbModel(doc), { transaction: t }); + return doc.properties; + }); } - public clearRange( + public async clearRange( context: Context, blob: BlobModel, start: number, end: number, leaseAccessConditions?: Models.LeaseAccessConditions, - modifiedAccessConditions?: Models.ModifiedAccessConditions + modifiedAccessConditions?: Models.ModifiedAccessConditions, + sequenceNumberAccessConditions?: Models.SequenceNumberAccessConditions ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const doc = await this.getBlobWithLeaseUpdated( + blob.accountName, + blob.containerName, + blob.name, + blob.snapshot, + context!, + false, + true, + t + ); + + validateWriteConditions(context, modifiedAccessConditions, doc); + + validateSequenceNumberWriteConditions( + context, + sequenceNumberAccessConditions, + doc + ); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + const lease = new BlobLeaseAdapter(doc); + new BlobWriteLeaseValidator(leaseAccessConditions).validate(lease, context); + + this.pageBlobRangesManager.clearRange(doc.pageRangesInOrder || [], { + start, + end + }); + + // TODO: Check other blob update operations need lease reset or not + // set lease state to available if it's expired + new BlobWriteLeaseSyncer(doc).sync(lease); + + doc.properties.etag = newEtag(); + doc.properties.lastModified = context.startTime || new Date(); + + await BlobsModel.upsert(this.convertBlobModelToDbModel(doc), { transaction: t }); + return doc.properties; + }); } - public getPageRanges( + public async getPageRanges( context: Context, account: string, container: string, @@ -2802,10 +3049,38 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - throw new Error("Method not implemented."); + const doc = await this.getBlobWithLeaseUpdated( + account, + container, + blob, + snapshot, + context, + false, + true + ); + + validateReadConditions(context, modifiedAccessConditions, doc); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + if (doc.properties.blobType !== Models.BlobType.PageBlob) { + throw StorageErrorFactory.getBlobInvalidBlobType(context.contextId); + } + + new BlobReadLeaseValidator(leaseAccessConditions).validate( + new BlobLeaseAdapter(doc), + context + ); + + return { + properties: doc.properties, + pageRangesInOrder: doc.pageRangesInOrder + }; } - public resizePageBlob( + public async resizePageBlob( context: Context, account: string, container: string, @@ -2814,21 +3089,145 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const doc = await this.getBlobWithLeaseUpdated( + account, + container, + blob, + undefined, + context, + false, + true, + t + ); + + validateWriteConditions(context, modifiedAccessConditions, doc); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + if (doc.properties.blobType !== Models.BlobType.PageBlob) { + throw StorageErrorFactory.getInvalidOperation( + context.contextId, + "Resize could only be against a page blob." + ); + } + + const lease = new BlobLeaseAdapter(doc); + new BlobWriteLeaseValidator(leaseAccessConditions).validate(lease, context); + + doc.pageRangesInOrder = doc.pageRangesInOrder || []; + if (doc.properties.contentLength! > blobContentLength) { + const start = blobContentLength; + const end = doc.properties.contentLength! - 1; + this.pageBlobRangesManager.clearRange(doc.pageRangesInOrder || [], { + start, + end + }); + } + + doc.properties.contentLength = blobContentLength; + doc.properties.lastModified = context.startTime || new Date(); + doc.properties.etag = newEtag(); + + new BlobWriteLeaseSyncer(doc).sync(lease); + + await BlobsModel.upsert(this.convertBlobModelToDbModel(doc), { transaction: t }); + return doc.properties; + }); } - public updateSequenceNumber( + public async updateSequenceNumber( context: Context, account: string, container: string, blob: string, sequenceNumberAction: Models.SequenceNumberActionType, - blobSequenceNumber: number | undefined + blobSequenceNumber: number | undefined, + leaseAccessConditions?: Models.LeaseAccessConditions, + modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const doc = await this.getBlobWithLeaseUpdated( + account, + container, + blob, + undefined, + context, + false, + true, + t + ); + + validateWriteConditions(context, modifiedAccessConditions, doc); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + if (doc.properties.blobType !== Models.BlobType.PageBlob) { + throw StorageErrorFactory.getInvalidOperation( + context.contextId!, + "Get Page Ranges could only be against a page blob." + ); + } + + const lease = new BlobLeaseAdapter(doc); + new BlobWriteLeaseValidator(leaseAccessConditions).validate(lease, context); + + if (doc.properties.blobSequenceNumber === undefined) { + doc.properties.blobSequenceNumber = 0; + } + + switch (sequenceNumberAction) { + case Models.SequenceNumberActionType.Max: + if (blobSequenceNumber === undefined) { + throw StorageErrorFactory.getInvalidOperation( + context.contextId!, + "x-ms-blob-sequence-number is required when x-ms-sequence-number-action is set to max." + ); + } + doc.properties.blobSequenceNumber = Math.max( + doc.properties.blobSequenceNumber, + blobSequenceNumber + ); + break; + case Models.SequenceNumberActionType.Increment: + if (blobSequenceNumber !== undefined) { + throw StorageErrorFactory.getInvalidOperation( + context.contextId!, + "x-ms-blob-sequence-number cannot be provided when x-ms-sequence-number-action is set to increment." + ); + } + doc.properties.blobSequenceNumber++; + break; + case Models.SequenceNumberActionType.Update: + if (blobSequenceNumber === undefined) { + throw StorageErrorFactory.getInvalidOperation( + context.contextId!, + "x-ms-blob-sequence-number is required when x-ms-sequence-number-action is set to update." + ); + } + doc.properties.blobSequenceNumber = blobSequenceNumber; + break; + default: + throw StorageErrorFactory.getInvalidOperation( + context.contextId!, + "Unsupported x-ms-sequence-number-action value." + ); + } + + doc.properties.etag = newEtag(); + doc.properties.lastModified = context.startTime!; + new BlobWriteLeaseSyncer(doc).sync(lease); + + await BlobsModel.upsert(this.convertBlobModelToDbModel(doc), { transaction: t }); + return doc.properties; + }); } - public appendBlock( + public async appendBlock( context: Context, block: BlockModel, leaseAccessConditions?: Models.LeaseAccessConditions | undefined, @@ -2837,14 +3236,85 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { | Models.AppendPositionAccessConditions | undefined ): Promise { - throw new Error("Method not implemented."); + return await this.sequelize.transaction(async (t) => { + const doc = await this.getBlobWithLeaseUpdated( + block.accountName, + block.containerName, + block.blobName, + undefined, + context, + false, + true + ); + + validateWriteConditions(context, modifiedAccessConditions, doc); + + if (!doc) { + throw StorageErrorFactory.getBlobNotFound(context.contextId); + } + + new BlobWriteLeaseValidator(leaseAccessConditions).validate( + new BlobLeaseAdapter(doc), + context + ); + + if (doc.properties.blobType !== Models.BlobType.AppendBlob) { + throw StorageErrorFactory.getBlobInvalidBlobType(context.contextId); + } + + if ( + (doc.committedBlocksInOrder || []).length >= MAX_APPEND_BLOB_BLOCK_COUNT + ) { + throw StorageErrorFactory.getBlockCountExceedsLimit(context.contextId); + } + + if ( + appendPositionAccessConditions !== undefined && + appendPositionAccessConditions.appendPosition !== undefined + ) { + if ( + (doc.properties.contentLength || 0) !== + appendPositionAccessConditions.appendPosition + ) { + throw StorageErrorFactory.getAppendPositionConditionNotMet( + context.contextId + ); + } + } + + if ( + appendPositionAccessConditions !== undefined && + appendPositionAccessConditions.maxSize !== undefined + ) { + if ( + (doc.properties.contentLength || 0) + block.size > + appendPositionAccessConditions.maxSize + ) { + throw StorageErrorFactory.getMaxBlobSizeConditionNotMet( + context.contextId + ); + } + } + + doc.committedBlocksInOrder = doc.committedBlocksInOrder || []; + doc.committedBlocksInOrder.push(block); + doc.properties.etag = newEtag(); + doc.properties.lastModified = context.startTime || new Date(); + doc.properties.contentLength = + (doc.properties.contentLength || 0) + block.size; + + await BlobsModel.upsert(this.convertBlobModelToDbModel(doc), { + transaction: t + }); + return doc.properties; + }); } public async listUncommittedBlockPersistencyChunks( marker: string = "-1", maxResults: number = 2000 ): Promise<[IExtentChunk[], string | undefined]> { - return BlocksModel.findAll({ + return await BlocksModel.findAll({ attributes: ["id", "persistency"], where: { id: { @@ -3079,9 +3549,8 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { } private convertDbModelToBlobModel(dbModel: BlobsModel): BlobModel { - const contentProperties: IBlobContentProperties = this.convertDbModelToBlobContentProperties( - dbModel - ); + const properties: Models.BlobPropertiesInternal = + this.convertDbModelToBlobProperties(dbModel); const lease = this.convertDbModelToLease(dbModel); @@ -3091,52 +3560,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { name: this.getModelValue(dbModel, "blobName", true), snapshot: this.getModelValue(dbModel, "snapshot", true), isCommitted: this.getModelValue(dbModel, "isCommitted", true), - properties: { - lastModified: this.getModelValue(dbModel, "lastModified", true), - etag: this.getModelValue(dbModel, "etag", true), - leaseDuration: lease.leaseDurationType, - creationTime: this.getModelValue(dbModel, "creationTime"), - leaseState: lease.leaseState, - leaseStatus: lease.leaseStatus, - accessTier: this.getModelValue( - dbModel, - "accessTier" - ), - accessTierInferred: this.getModelValue( - dbModel, - "accessTierInferred" - ), - accessTierChangeTime: this.getModelValue( - dbModel, - "accessTierChangeTime" - ), - blobSequenceNumber: this.getModelValue( - dbModel, - "blobSequenceNumber" - ), - blobType: this.getModelValue(dbModel, "blobType"), - contentMD5: contentProperties - ? this.restoreUint8Array(contentProperties.contentMD5) - : undefined, - contentDisposition: contentProperties - ? contentProperties.contentDisposition - : undefined, - contentEncoding: contentProperties - ? contentProperties.contentEncoding - : undefined, - contentLanguage: contentProperties - ? contentProperties.contentLanguage - : undefined, - contentLength: contentProperties - ? contentProperties.contentLength - : undefined, - contentType: contentProperties - ? contentProperties.contentType - : undefined, - cacheControl: contentProperties - ? contentProperties.cacheControl - : undefined - }, + properties: properties, leaseDurationSeconds: lease.leaseDurationSeconds, leaseBreakTime: lease.leaseBreakTime, leaseExpireTime: lease.leaseExpireTime, @@ -3146,69 +3570,57 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { dbModel, "committedBlocksInOrder" ), + pageRangesInOrder: this.deserializeModelValue(dbModel, "pageRangesInOrder"), metadata: this.deserializeModelValue(dbModel, "metadata"), blobTags: this.deserializeModelValue(dbModel, "blobTags") }; } private convertBlobModelToDbModel(blob: BlobModel): any { - const contentProperties = this.convertBlobContentPropertiesToDbModel( - blob.properties - ); - const lease = this.convertLeaseToDbModel(new BlobLeaseAdapter(blob)); return { accountName: blob.accountName, containerName: blob.containerName, blobName: blob.name, snapshot: blob.snapshot, - blobType: blob.properties.blobType, - blobSequenceNumber: blob.properties.blobSequenceNumber || null, isCommitted: blob.isCommitted, - lastModified: blob.properties.lastModified, - creationTime: blob.properties.creationTime || null, - etag: blob.properties.etag, - accessTier: blob.properties.accessTier || null, - accessTierChangeTime: blob.properties.accessTierChangeTime || null, - accessTierInferred: blob.properties.accessTierInferred || null, - leaseBreakExpireTime: blob.leaseBreakTime || null, - leaseExpireTime: blob.leaseExpireTime || null, - leaseId: blob.leaseId || null, - leasedurationNumber: blob.leaseDurationSeconds || null, - leaseDuration: blob.properties.leaseDuration || null, - leaseStatus: blob.properties.leaseStatus || null, - leaseState: blob.properties.leaseState || null, + properties: this.serializeModelValue(blob.properties) || null, ...lease, persistency: this.serializeModelValue(blob.persistency) || null, committedBlocksInOrder: this.serializeModelValue(blob.committedBlocksInOrder) || null, + pageRangesInOrder: + this.serializeModelValue(blob.pageRangesInOrder) || null, metadata: this.serializeModelValue(blob.metadata) || null, blobTags: this.serializeModelValue(blob.blobTags) || null, - ...contentProperties }; } - private convertDbModelToBlobContentProperties( + private convertDbModelToBlobProperties( dbModel: BlobsModel - ): IBlobContentProperties { - return this.deserializeModelValue(dbModel, "contentProperties"); - } - - private convertBlobContentPropertiesToDbModel( - contentProperties: IBlobContentProperties - ): object { - return { - contentProperties: - this.serializeModelValue({ - contentLength: contentProperties.contentLength, - contentType: contentProperties.contentType, - contentEncoding: contentProperties.contentEncoding, - contentLanguage: contentProperties.contentLanguage, - contentMD5: contentProperties.contentMD5, - contentDisposition: contentProperties.contentDisposition, - cacheControl: contentProperties.cacheControl - }) || null + ): Models.BlobPropertiesInternal { + const propertiesDbModel = this.deserializeModelValue(dbModel, "properties"); + const properties: Models.BlobPropertiesInternal = { + ...propertiesDbModel, + lastModified: propertiesDbModel.lastModified + ? new Date(propertiesDbModel.lastModified) + : undefined, + copyCompletionTime: propertiesDbModel.copyCompletionTime + ? new Date(propertiesDbModel.copyCompletionTime) + : undefined, + creationTime: propertiesDbModel.creationTime + ? new Date(propertiesDbModel.creationTime) + : undefined, + deletedTime: propertiesDbModel.deletedTime + ? new Date(propertiesDbModel.deletedTime) + : undefined, + expiresOn: propertiesDbModel.expiresOn + ? new Date(propertiesDbModel.expiresOn) + : undefined, + contentMD5: this.restoreUint8Array(propertiesDbModel.contentMD5) }; + + return properties; } private convertDbModelToLease(dbModel: ContainersModel | BlobsModel): ILease { @@ -3319,7 +3731,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { return doc; } - public setBlobTag( + public async setBlobTag( context: Context, account: string, container: string, @@ -3329,7 +3741,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { tags: Models.BlobTags | undefined, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ @@ -3387,7 +3799,7 @@ export default class SqlBlobMetadataStore implements IBlobMetadataStore { leaseAccessConditions?: Models.LeaseAccessConditions, modifiedAccessConditions?: Models.ModifiedAccessConditions ): Promise { - return this.sequelize.transaction(async (t) => { + return await this.sequelize.transaction(async (t) => { await this.assertContainerExists(context, account, container, t); const blobFindResult = await BlobsModel.findOne({ diff --git a/tests/blob/apis/appendblob.test.ts b/tests/blob/apis/appendblob.test.ts index 10e38822b..e07fd98bb 100644 --- a/tests/blob/apis/appendblob.test.ts +++ b/tests/blob/apis/appendblob.test.ts @@ -68,7 +68,7 @@ describe("AppendBlobAPIs", () => { await containerClient.delete(); }); - it("Create append blob should work @loki", async () => { + it("Create append blob should work @loki @sql", async () => { await appendBlobClient.create(); const properties = await appendBlobClient.getProperties(); assert.deepStrictEqual(properties.blobType, "AppendBlob"); @@ -85,7 +85,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.blobCommittedBlockCount, 0); }); - it("Create append blob override existing pageblob @loki", async () => { + it("Create append blob override existing pageblob @loki @sql", async () => { const pageBlobClient = blobClient.getPageBlobClient(); await pageBlobClient.create(512); @@ -133,12 +133,12 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.blobCommittedBlockCount, 0); }); - it("Delete append blob should work @loki", async () => { + it("Delete append blob should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.delete(); }); - it("Create append blob snapshot should work @loki", async () => { + it("Create append blob snapshot should work @loki @sql", async () => { await appendBlobClient.create(); const response = await appendBlobClient.createSnapshot(); const appendBlobSnapshotClient = appendBlobClient.withSnapshot( @@ -176,7 +176,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.blobCommittedBlockCount, 0); }); - it("Copy append blob snapshot should work @loki", async () => { + it("Copy append blob snapshot should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("hello", 5); @@ -212,7 +212,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.copyStatus, "success"); }); - it("Synchronized copy append blob snapshot should work @loki", async () => { + it("Synchronized copy append blob snapshot should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("hello", 5); @@ -247,7 +247,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.copySource, appendBlobSnapshotClient.url); }); - it("Set append blob metadata should work @loki", async () => { + it("Set append blob metadata should work @loki @sql", async () => { await appendBlobClient.create(); const metadata = { @@ -260,7 +260,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(properties.metadata, metadata); }); - it("Set append blob HTTP headers should work @loki", async () => { + it("Set append blob HTTP headers should work @loki @sql", async () => { await appendBlobClient.create(); const md5 = new Uint8Array([1, 2, 3, 4, 5]); @@ -292,7 +292,7 @@ describe("AppendBlobAPIs", () => { ); }); - it("Set tier should not work for append blob @loki", async function () { + it("Set tier should not work for append blob @loki @sql", async function () { await appendBlobClient.create(); try { await blobClient.setAccessTier("hot"); @@ -302,7 +302,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block should work @loki", async () => { + it("Append block should work @loki @sql", async () => { await appendBlobClient.create(); let appendBlockResponse = await appendBlobClient.appendBlock("abcdef", 6); assert.deepStrictEqual(appendBlockResponse.blobAppendOffset, "0"); @@ -357,7 +357,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(string, "abcdef123456T@"); }); - it("Download append blob should work @loki", async () => { + it("Download append blob should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("abcdef", 6); await appendBlobClient.appendBlock("123456", 6); @@ -374,7 +374,7 @@ describe("AppendBlobAPIs", () => { assert.deepStrictEqual(response.contentRange, "bytes 5-12/14"); }); - it("Download append blob should work for snapshot @loki", async () => { + it("Download append blob should work for snapshot @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("abcdef", 6); @@ -393,7 +393,7 @@ describe("AppendBlobAPIs", () => { assert.deepEqual(response.contentMD5, await getMD5FromString("def")); }); - it("Download append blob should work for copied blob @loki", async () => { + it("Download append blob should work for copied blob @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("abcdef", 6); @@ -410,7 +410,7 @@ describe("AppendBlobAPIs", () => { assert.deepEqual(response.contentMD5, await getMD5FromString("def")); }); - it("Append block with invalid blob type should not work @loki", async () => { + it("Append block with invalid blob type should not work @loki @sql", async () => { const pageBlobClient = appendBlobClient.getPageBlobClient(); await pageBlobClient.create(512); @@ -423,7 +423,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block with content length 0 should not work @loki", async () => { + it("Append block with content length 0 should not work @loki @sql", async () => { await appendBlobClient.create(); try { @@ -435,7 +435,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block append position access condition should work @loki", async () => { + it("Append block append position access condition should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("a", 1, { conditions: { @@ -480,7 +480,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block md5 validation should work @loki", async () => { + it("Append block md5 validation should work @loki @sql", async () => { await appendBlobClient.create(); await appendBlobClient.appendBlock("aEf", 1, { transactionalContentMD5: await getMD5FromString("aEf") @@ -498,7 +498,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block access condition should work @loki", async () => { + it("Append block access condition should work @loki @sql", async () => { let response = await appendBlobClient.create(); response = await appendBlobClient.appendBlock("a", 1, { conditions: { @@ -538,7 +538,7 @@ describe("AppendBlobAPIs", () => { assert.fail(); }); - it("Append block lease condition should work @loki", async () => { + it("Append block lease condition should work @loki @sql", async () => { await appendBlobClient.create(); const leaseId = "abcdefg"; diff --git a/tests/blob/apis/blob.test.ts b/tests/blob/apis/blob.test.ts index c21212ed9..d5f523138 100644 --- a/tests/blob/apis/blob.test.ts +++ b/tests/blob/apis/blob.test.ts @@ -840,7 +840,7 @@ describe("BlobAPIs", () => { } }); - it("Upload blob with accesstier should get accessTierInferred as false @loki", async () => { + it("Upload blob with accesstier should get accessTierInferred as false @loki @sql", async () => { const blobName = getUniqueName("blob"); const blobClient = containerClient.getBlockBlobClient(blobName); @@ -896,7 +896,7 @@ describe("BlobAPIs", () => { ); }); - it("Copy blob should work @loki", async () => { + it("Copy blob should work @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -955,7 +955,7 @@ describe("BlobAPIs", () => { ); }); - it("Copy blob should work to override metadata @loki", async () => { + it("Copy blob should work to override metadata @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -979,7 +979,7 @@ describe("BlobAPIs", () => { assert.deepStrictEqual(result.metadata, metadata2); }); - it("Copy blob should work with source archive blob and accesstier header @loki, @sql", async () => { + it("Copy blob should work with source archive blob and accesstier header @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1014,7 +1014,7 @@ describe("BlobAPIs", () => { assert.deepStrictEqual(result.accessTier, "Hot"); }); - it("Copy blob should not override destination Lease status @loki", async () => { + it("Copy blob should not override destination Lease status @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1049,7 +1049,7 @@ describe("BlobAPIs", () => { await destLeaseClient.releaseLease(); }); - it("Copy blob should work for page blob @loki", async () => { + it("Copy blob should work for page blob @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1108,7 +1108,7 @@ describe("BlobAPIs", () => { ); }); - it("Copy blob should not work for page blob and set tier @loki", async () => { + it("Copy blob should not work for page blob and set tier @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1146,7 +1146,7 @@ describe("BlobAPIs", () => { assert.deepStrictEqual(err.statusCode, 400); }); - it("Copy blob should fail with 400 when copy source is invalid @loki", async () => { + it("Copy blob should fail with 400 when copy source is invalid @loki @sql", async () => { const destBlob = getUniqueName("blob"); const destBlobClient = containerClient.getBlockBlobClient(destBlob); @@ -1163,7 +1163,7 @@ describe("BlobAPIs", () => { assert.fail(); }); - it("Copy blob should not work with ifNoneMatch * when dest exist @loki", async () => { + it("Copy blob should not work with ifNoneMatch * when dest exist @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1234,7 +1234,7 @@ describe("BlobAPIs", () => { assert.fail(); }); - it("Synchronized copy blob should work @loki", async () => { + it("Synchronized copy blob should work @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1292,7 +1292,7 @@ describe("BlobAPIs", () => { ); }); - it("Synchronized copy blob should work to override metadata @loki", async () => { + it("Synchronized copy blob should work to override metadata @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1316,7 +1316,7 @@ describe("BlobAPIs", () => { assert.deepStrictEqual(result.metadata, metadata2); }); - it("Synchronized copy blob should not override destination Lease status @loki", async () => { + it("Synchronized copy blob should not override destination Lease status @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1351,7 +1351,7 @@ describe("BlobAPIs", () => { await destLeaseClient.releaseLease(); }); - it("Synchronized copy blob should work for page blob @loki", async () => { + it("Synchronized copy blob should work for page blob @loki @sql", async () => { const sourceBlob = getUniqueName("blob"); const destBlob = getUniqueName("blob"); @@ -1590,7 +1590,7 @@ describe("BlobAPIs", () => { blockBlobClient2.delete(); }); - it("set blob tag should work in create page/append blob, copyFromURL. @loki", async () => { + it("set blob tag should work in create page/append blob, copyFromURL. @loki @sql", async () => { const tags = { tag1: "val1", tag2: "val2", diff --git a/tests/blob/apis/container.test.ts b/tests/blob/apis/container.test.ts index 64cf39880..b2fead044 100644 --- a/tests/blob/apis/container.test.ts +++ b/tests/blob/apis/container.test.ts @@ -563,8 +563,7 @@ describe("ContainerAPIs", () => { } }); - // TODO: azure/storage-blob 12.9.0 will fail on list uncimmited blob from container, will skip the case until this is fix in SDK or Azurite - it.skip("should only show uncommitted blobs in listBlobFlatSegment with uncommittedblobs option @loki @sql", async () => { + it("should only show uncommitted blobs in listBlobFlatSegment with uncommittedblobs option @loki @sql", async () => { const blobClient = containerClient.getBlobClient( getUniqueName("uncommittedblob") ); @@ -1043,7 +1042,7 @@ describe("ContainerAPIs", () => { await blockBlobClient.upload("", 0); blobClients.push(blobClient); } - blobClients[0].createSnapshot(); + await blobClients[0].createSnapshot(); // create account sas const storageSharedKeyCredential = new StorageSharedKeyCredential( diff --git a/tests/blob/apis/pageblob.test.ts b/tests/blob/apis/pageblob.test.ts index 570cfef0e..09ab9793c 100644 --- a/tests/blob/apis/pageblob.test.ts +++ b/tests/blob/apis/pageblob.test.ts @@ -66,7 +66,7 @@ describe("PageBlobAPIs", () => { await containerClient.delete(); }); - it("create with default parameters @loki", async () => { + it("create with default parameters @loki @sql", async () => { const reuslt_create = await pageBlobClient.create(512); assert.equal( reuslt_create._response.request.headers.get("x-ms-client-request-id"), @@ -84,7 +84,7 @@ describe("PageBlobAPIs", () => { ); }); - it("create with all parameters set @loki", async () => { + it("create with all parameters set @loki @sql", async () => { const options = { blobHTTPHeaders: { blobCacheControl: "blobCacheControl", @@ -144,7 +144,7 @@ describe("PageBlobAPIs", () => { ); }); - it("download page blob with partial ranges @loki", async () => { + it("download page blob with partial ranges @loki @sql", async () => { const length = 512 * 10; await pageBlobClient.create(length); @@ -171,7 +171,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(result._response.status, 206); }); - it("download page blob with no ranges uploaded @loki", async () => { + it("download page blob with no ranges uploaded @loki @sql", async () => { const length = 512 * 10; await pageBlobClient.create(length); @@ -194,7 +194,7 @@ describe("PageBlobAPIs", () => { ); }); - it("download page blob with no ranges uploaded after resize to bigger size @loki", async () => { + it("download page blob with no ranges uploaded after resize to bigger size @loki @sql", async () => { let length = 512 * 10; await pageBlobClient.create(length); @@ -237,7 +237,7 @@ describe("PageBlobAPIs", () => { ); }); - it("download page blob with no ranges uploaded after resize to smaller size @loki", async () => { + it("download page blob with no ranges uploaded after resize to smaller size @loki @sql", async () => { let length = 512 * 10; await pageBlobClient.create(length); @@ -268,7 +268,7 @@ describe("PageBlobAPIs", () => { ); }); - it("uploadPages @loki", async () => { + it("uploadPages @loki @sql", async () => { await pageBlobClient.create(1024); const result = await blobClient.download(0); @@ -292,7 +292,7 @@ describe("PageBlobAPIs", () => { assert.equal(await bodyToString(page2, 512), "b".repeat(512)); }); - it("uploadPages should work with sequence number conditions @loki", async () => { + it("uploadPages should work with sequence number conditions @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( @@ -327,7 +327,7 @@ describe("PageBlobAPIs", () => { assert.equal(await bodyToString(page2, 512), "b".repeat(512)); }); - it("uploadPages should not work if ifSequenceNumberEqualTo doesn't match @loki", async () => { + it("uploadPages should not work if ifSequenceNumberEqualTo doesn't match @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( @@ -349,7 +349,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("uploadPages should not work if ifSequenceNumberLessThan doesn't match @loki", async () => { + it("uploadPages should not work if ifSequenceNumberLessThan doesn't match @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( @@ -382,7 +382,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("uploadPages should not work if ifSequenceNumberLessThanOrEqualTo doesn't match @loki", async () => { + it("uploadPages should not work if ifSequenceNumberLessThanOrEqualTo doesn't match @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( @@ -410,7 +410,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("uploadPages with sequential pages @loki", async () => { + it("uploadPages with sequential pages @loki @sql", async () => { const length = 512 * 3; await pageBlobClient.create(length); @@ -443,7 +443,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![2], { offset: 1024, count: 511 }); }); - it("uploadPages with one big page range @loki", async () => { + it("uploadPages with one big page range @loki @sql", async () => { const length = 512 * 3; await pageBlobClient.create(length); @@ -476,7 +476,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![0], { offset: 0, count: 1535 }); }); - it("uploadPages with non-sequential pages @loki", async () => { + it("uploadPages with non-sequential pages @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -515,7 +515,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![1], { offset: 1536, count: 511 }); }); - it("uploadPages to internally override a sequential range @loki", async () => { + it("uploadPages to internally override a sequential range @loki @sql", async () => { const length = 512 * 3; await pageBlobClient.create(length); @@ -552,7 +552,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![2], { offset: 1024, count: 511 }); }); - it("uploadPages to internally right align override a sequential range @loki", async () => { + it("uploadPages to internally right align override a sequential range @loki @sql", async () => { const length = 512 * 3; await pageBlobClient.create(length); @@ -588,7 +588,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![1], { offset: 1024, count: 511 }); }); - it("uploadPages to internally left align override a sequential range @loki", async () => { + it("uploadPages to internally left align override a sequential range @loki @sql", async () => { const length = 512 * 3; await pageBlobClient.create(length); @@ -624,7 +624,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![1], { offset: 512, count: 1023 }); }); - it("uploadPages to totally override a sequential range @loki", async () => { + it("uploadPages to totally override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -678,7 +678,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to left override a sequential range @loki", async () => { + it("uploadPages to left override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -722,7 +722,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual(ranges.pageRange![1], { offset: 1024, count: 1023 }); }); - it("uploadPages to right override a sequential range @loki", async () => { + it("uploadPages to right override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -772,7 +772,7 @@ describe("PageBlobAPIs", () => { }); }); - it("resize override a sequential range @loki", async () => { + it("resize override a sequential range @loki @sql", async () => { let length = 512 * 3; await pageBlobClient.create(length); @@ -815,7 +815,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to internally override a non-sequential range @loki", async () => { + it("uploadPages to internally override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -867,7 +867,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to internally insert into a non-sequential range @loki", async () => { + it("uploadPages to internally insert into a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -919,7 +919,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to totally override a non-sequential range @loki", async () => { + it("uploadPages to totally override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -963,7 +963,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to left override a non-sequential range @loki", async () => { + it("uploadPages to left override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1011,7 +1011,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to insert into a non-sequential range @loki", async () => { + it("uploadPages to insert into a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1063,7 +1063,7 @@ describe("PageBlobAPIs", () => { }); }); - it("uploadPages to right override a non-sequential range @loki", async () => { + it("uploadPages to right override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1111,7 +1111,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages @loki", async () => { + it("clearPages @loki @sql", async () => { await pageBlobClient.create(1024); let result = await blobClient.download(0); assert.deepStrictEqual( @@ -1135,7 +1135,7 @@ describe("PageBlobAPIs", () => { ); }); - it("clearPages should work with sequence number conditions @loki", async () => { + it("clearPages should work with sequence number conditions @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.clearPages(0, 512, { conditions: { @@ -1146,7 +1146,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages should not work with invalid ifSequenceNumberEqualTo @loki", async () => { + it("clearPages should not work with invalid ifSequenceNumberEqualTo @loki @sql", async () => { await pageBlobClient.create(1024); try { await pageBlobClient.clearPages(0, 512, { @@ -1161,7 +1161,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("clearPages should not work with invalid ifSequenceNumberLessThan @loki", async () => { + it("clearPages should not work with invalid ifSequenceNumberLessThan @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( SequenceNumberActionType.Increment @@ -1186,7 +1186,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("clearPages should not work with invalid ifSequenceNumberLessThanOrEqualTo @loki", async () => { + it("clearPages should not work with invalid ifSequenceNumberLessThanOrEqualTo @loki @sql", async () => { await pageBlobClient.create(1024); await pageBlobClient.updateSequenceNumber( SequenceNumberActionType.Increment @@ -1211,7 +1211,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("clearPages to internally override a sequential range @loki", async () => { + it("clearPages to internally override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1261,7 +1261,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages to totally override a sequential range @loki", async () => { + it("clearPages to totally override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1303,7 +1303,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual((ranges.clearRange || []).length, 0); }); - it("clearPages to left override a sequential range @loki", async () => { + it("clearPages to left override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1349,7 +1349,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages to right override a sequential range @loki", async () => { + it("clearPages to right override a sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1395,7 +1395,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages to internally override a non-sequential range @loki", async () => { + it("clearPages to internally override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1443,7 +1443,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages to internally insert into a non-sequential range @loki", async () => { + it("clearPages to internally insert into a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1495,7 +1495,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages will fail when start range longer than blob length @loki", async () => { + it("clearPages will fail when start range longer than blob length @loki @sql", async () => { const length = 512 * 2; await pageBlobClient.create(length); @@ -1515,7 +1515,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("GetPageRanges will fail when start range longer than blob length @loki", async () => { + it("GetPageRanges will fail when start range longer than blob length @loki @sql", async () => { const length = 512 * 2; await pageBlobClient.create(length); @@ -1535,7 +1535,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("UploadPages will fail when start range longer than blob length @loki", async () => { + it("UploadPages will fail when start range longer than blob length @loki @sql", async () => { const length = 512 * 2; await pageBlobClient.create(length); @@ -1554,7 +1554,7 @@ describe("PageBlobAPIs", () => { assert.fail(); }); - it("clearPages to totally override a non-sequential range @loki", async () => { + it("clearPages to totally override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1594,7 +1594,7 @@ describe("PageBlobAPIs", () => { assert.deepStrictEqual((ranges.clearRange || []).length, 0); }); - it("clearPages to left override a non-sequential range @loki", async () => { + it("clearPages to left override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1641,7 +1641,7 @@ describe("PageBlobAPIs", () => { }); }); - it("clearPages to right override a non-sequential range @loki", async () => { + it("clearPages to right override a non-sequential range @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); @@ -1684,7 +1684,7 @@ describe("PageBlobAPIs", () => { }); }); - it("getPageRanges @loki", async () => { + it("getPageRanges @loki @sql", async () => { await pageBlobClient.create(1024); const result = await blobClient.download(0); @@ -1704,7 +1704,7 @@ describe("PageBlobAPIs", () => { assert.equal(page2.pageRange![0].count, 511); }); - it("updateSequenceNumber @loki", async () => { + it("updateSequenceNumber @loki @sql", async () => { await pageBlobClient.create(1024); let propertiesResponse = await pageBlobClient.getProperties(); @@ -1726,7 +1726,7 @@ describe("PageBlobAPIs", () => { }); // devstoreaccount1 is standard storage account which doesn't support premium page blob tiers - it.skip("setAccessTier for Page blob @loki", async () => { + it.skip("setAccessTier for Page blob @loki @sql", async () => { const length = 512 * 5; await pageBlobClient.create(length); let propertiesResponse = await pageBlobClient.getProperties(); diff --git a/tests/blob/apis/service.test.ts b/tests/blob/apis/service.test.ts index faf7c6e1c..f7d0ba428 100644 --- a/tests/blob/apis/service.test.ts +++ b/tests/blob/apis/service.test.ts @@ -459,7 +459,7 @@ describe("ServiceAPIs", () => { ); }); - it("Get Blob service stats negative @loki", async () => { + it("Get Blob service stats negative @loki @sql", async () => { await serviceClient.getStatistics() .catch((err) => { assert.strictEqual(err.statusCode, 400); @@ -498,7 +498,7 @@ describe("ServiceAPIs - secondary location endpoint", () => { await server.clean(); }); - it("Get Blob service stats @loki", async () => { + it("Get Blob service stats @loki @sql", async () => { await serviceClient.getStatistics() .then((result) => { diff --git a/tests/blob/handlers/AppendBlobHandler.test.ts b/tests/blob/handlers/AppendBlobHandler.test.ts index 2c4878860..9e0636de1 100644 --- a/tests/blob/handlers/AppendBlobHandler.test.ts +++ b/tests/blob/handlers/AppendBlobHandler.test.ts @@ -96,7 +96,7 @@ describe("AppendBlobHandler", () => { ).thenResolve(extent); describe("create", () => { - it("accepts requests withContent-Length == 0 @loki", async () => { + it("accepts requests withContent-Length == 0 @loki @sql", async () => { const handler = new AppendBlobHandler( instance(metadataStore), instance(extentStore), @@ -108,7 +108,7 @@ describe("AppendBlobHandler", () => { }); }); - it("accepts requests with Content-Length != 0 in loose mode @loki", async () => { + it("accepts requests with Content-Length != 0 in loose mode @loki @sql", async () => { const handler = new AppendBlobHandler( instance(metadataStore), instance(extentStore), @@ -120,7 +120,7 @@ describe("AppendBlobHandler", () => { }); }); - it("rejects requests with Content-Length != 0 @loki", async () => { + it("rejects requests with Content-Length != 0 @loki @sql", async () => { const handler = new AppendBlobHandler( instance(metadataStore), instance(extentStore), @@ -146,7 +146,7 @@ describe("AppendBlobHandler", () => { bufferStream.end(buffer); }); - it("accepts requests with Content-Length != 0 @loki", async () => { + it("accepts requests with Content-Length != 0 @loki @sql", async () => { const handler = new AppendBlobHandler( instance(metadataStore), instance(extentStore), @@ -158,7 +158,7 @@ describe("AppendBlobHandler", () => { }); }); - it("rejects requests with Content-Length == 0 @loki", async () => { + it("rejects requests with Content-Length == 0 @loki @sql", async () => { const handler = new AppendBlobHandler( instance(metadataStore), instance(extentStore), @@ -176,7 +176,7 @@ describe("AppendBlobHandler", () => { ); }); - it("accepts requests with valid MD5 checksum @loki", async () => { + it("accepts requests with valid MD5 checksum @loki @sql", async () => { when(request.getHeader(HeaderConstants.CONTENT_MD5)).thenReturn( "T0EkOEfaaTpPNWwEhhFLxg==" ); @@ -195,7 +195,7 @@ describe("AppendBlobHandler", () => { }); }); - it("rejects requests with invalid MD5 checksum @loki", async () => { + it("rejects requests with invalid MD5 checksum @loki @sql", async () => { when(request.getHeader(HeaderConstants.CONTENT_MD5)).thenReturn( "d3JvbmdfTUQ1X2NoZWNrc3VtCg==" ); diff --git a/tests/blob/pagewithdelimiter.test.ts b/tests/blob/pagewithdelimiter.test.ts index 4416adc5b..703c63554 100644 --- a/tests/blob/pagewithdelimiter.test.ts +++ b/tests/blob/pagewithdelimiter.test.ts @@ -36,31 +36,31 @@ describe("PageWithDelimiter", () => { "e/2" ]; - it("handles no blob results @loki", async () => { + it("handles no blob results @loki @sql", async () => { const page = new PageWithDelimiter(5); const [items, prefixes, marker] = await page.fill(createReader([], 5), namer); checkResult(items, prefixes, marker, 0, 0, ""); }); - it("fills 1 result properly @loki", async () => { + it("fills 1 result properly @loki @sql", async () => { const page = new PageWithDelimiter(1); const [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); checkResult(items, prefixes, marker, 1, 0, "a"); }); - it("fills n results properly @loki", async () => { + it("fills n results properly @loki @sql", async () => { const page = new PageWithDelimiter(5); const [items, prefixes, marker] = await page.fill(createReader(blobs, 5), namer); checkResult(items, prefixes, marker, 5, 0, "c/sub/1"); }); - it("fills exact count with no continuation @loki", async () => { + it("fills exact count with no continuation @loki @sql", async () => { const page = new PageWithDelimiter(blobs.length); const [items, prefixes, marker] = await page.fill(createReader(blobs, blobs.length), namer); checkResult(items, prefixes, marker, blobs.length, 0, ""); }); - it("fills smaller than max page with no continuation @loki", async () => { + it("fills smaller than max page with no continuation @loki @sql", async () => { const page = new PageWithDelimiter(blobs.length+1); const [items, prefixes, marker] = await page.fill(createReader(blobs, blobs.length+1), namer); checkResult(items, prefixes, marker, blobs.length, 0, ""); @@ -71,21 +71,21 @@ describe("PageWithDelimiter", () => { describe("and 1 item page size", () => { - it("handles no blob results @loki", async () => { + it("handles no blob results @loki @sql", async () => { const blobs: string[] = []; const page = new PageWithDelimiter(1, "/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); checkResult(items, prefixes, marker, 0, 0, ""); }); - it("handles 1 blob results @loki", async () => { + it("handles 1 blob results @loki @sql", async () => { const blobs = ["a"]; const page = new PageWithDelimiter(1, "/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); checkResult(items, prefixes, marker, 1, 0, ""); }); - it("returns 1 of 2 items with proper continuation @loki", async () => { + it("returns 1 of 2 items with proper continuation @loki @sql", async () => { const blobs = ["a", "b"]; const page = new PageWithDelimiter(1, "/"); let [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); @@ -97,14 +97,14 @@ describe("PageWithDelimiter", () => { checkResult(items, prefixes, marker, 1, 0, ""); }); - it("returns first item when prefixes exist @loki", async () => { + it("returns first item when prefixes exist @loki @sql", async () => { const blobs = ["a/1", "a/2", "a/3", "a/sub/1"]; const page = new PageWithDelimiter(1, "/", "a/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); checkResult(items, prefixes, marker, 1, 0, "a/1"); }); - it("returns first prefix when blobs exist @loki", async () => { + it("returns first prefix when blobs exist @loki @sql", async () => { const blobs = ["a/s0/1", "a/s0/2", "a/s0/3", "a/s1/1", "a/s2/2", "a/z"]; const page = new PageWithDelimiter(1, "/", "a/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 1), namer); @@ -114,21 +114,21 @@ describe("PageWithDelimiter", () => { describe("multiple item page size", () => { - it("squashes prefixes @loki", async () => { + it("squashes prefixes @loki @sql", async () => { const blobs = ["a/s0/1", "a/s0/2", "a/s0/3", "a/s1/1", "a/s1/2", "a/s2/2", "a/z"]; const page = new PageWithDelimiter(2, "/", "a/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 2), namer); checkResult(items, prefixes, marker, 0, 2, "a/s1/2"); }); - it("squashes a mix @loki", async () => { + it("squashes a mix @loki @sql", async () => { const blobs = ["a/a", "a/s0/1", "a/s0/2", "a/s1/1", "a/s1/2", "a/z"]; const page = new PageWithDelimiter(2, "/", "a/"); const [items, prefixes, marker] = await page.fill(createReader(blobs, 2), namer); checkResult(items, prefixes, marker, 1, 1, "a/s0/2"); }); - it("follows squashed pages @loki", async () => { + it("follows squashed pages @loki @sql", async () => { const blobs = ["a/a", "a/s0/1", "a/s0/2", "a/s1/1", "a/s1/2", "a/z"]; const page = new PageWithDelimiter(2, "/", "a/"); let [items, prefixes, marker] = await page.fill(createReader(blobs, 2), namer); @@ -140,7 +140,7 @@ describe("PageWithDelimiter", () => { checkResult(items, prefixes, marker, 1, 1, ""); }); - it("squashes within one larger page @loki", async () => { + it("squashes within one larger page @loki @sql", async () => { const blobs = ["a/a", "a/s0/1", "a/s0/2", "a/s1/1", "a/s1/2", "a/z"]; const page = new PageWithDelimiter(4, "/", "a/"); let [items, prefixes, marker] = await page.fill(createReader(blobs, 4), namer); diff --git a/tests/blob/sas.test.ts b/tests/blob/sas.test.ts index 39bfa441f..8e14de1a4 100644 --- a/tests/blob/sas.test.ts +++ b/tests/blob/sas.test.ts @@ -115,7 +115,13 @@ describe("Shared Access Signature (SAS) authentication", () => { await server.clean(); }); - it("generateAccountSASQueryParameters should generate correct hashes", async () => { + // This has multiple issues: + // 1. It depends on timezone need to set "TZ=Etc/GMT-1". + // 2. Even when setting timezone it still fails. + // 3. It tests storage-blob sdk rather than Azurite. + // 4. Ground truth sig shouldn't be hardcoded. + // So should be removed but skipping for now. + it.skip("generateAccountSASQueryParameters should generate correct hashes", async () => { const startDate = new Date(2022, 3, 16, 14, 31, 48, 0); const endDate = new Date(2022, 3, 17, 14, 31, 48, 0); @@ -370,7 +376,7 @@ describe("Shared Access Signature (SAS) authentication", () => { assert.ok(error); }); - it("Synchronized copy blob should work with write permission in account SAS to override an existing blob @loki", async () => { + it("Synchronized copy blob should work with write permission in account SAS to override an existing blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -418,7 +424,7 @@ describe("Shared Access Signature (SAS) authentication", () => { await blob2.syncCopyFromURL(blob1.url); }); - it("Synchronized copy blob shouldn't work without write permission in account SAS to override an existing blob @loki", async () => { + it("Synchronized copy blob shouldn't work without write permission in account SAS to override an existing blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -473,7 +479,7 @@ describe("Shared Access Signature (SAS) authentication", () => { assert.ok(error !== undefined); }); - it("Synchronized copy blob should work without write permission in account SAS to an nonexisting blob @loki", async () => { + it("Synchronized copy blob should work without write permission in account SAS to an nonexisting blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -520,7 +526,7 @@ describe("Shared Access Signature (SAS) authentication", () => { await blob2.syncCopyFromURL(blob1.url); }); - it("Copy blob should work with write permission in account SAS to override an existing blob @loki", async () => { + it("Copy blob should work with write permission in account SAS to override an existing blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -568,7 +574,7 @@ describe("Shared Access Signature (SAS) authentication", () => { await blob2.beginCopyFromURL(blob1.url); }); - it("Copy blob shouldn't work without write permission in account SAS to override an existing blob @loki", async () => { + it("Copy blob shouldn't work without write permission in account SAS to override an existing blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -623,7 +629,7 @@ describe("Shared Access Signature (SAS) authentication", () => { assert.ok(error !== undefined); }); - it("Copy blob should work without write permission in account SAS to an nonexisting blob @loki", async () => { + it("Copy blob should work without write permission in account SAS to an nonexisting blob @loki @sql", async () => { const tmr = new Date(); tmr.setDate(tmr.getDate() + 1); @@ -1255,7 +1261,7 @@ describe("Shared Access Signature (SAS) authentication", () => { await containerClient.delete(); }); - it("Synchronized copy blob should work with write permission in blob SAS to override an existing blob @loki", async () => { + it("Synchronized copy blob should work with write permission in blob SAS to override an existing blob @loki @sql", async () => { const now = new Date(); now.setMinutes(now.getMinutes() - 5); // Skip clock skew with server @@ -1302,7 +1308,7 @@ describe("Shared Access Signature (SAS) authentication", () => { await blob2SAS.syncCopyFromURL(blob1.url); }); - it("Synchronized copy blob shouldn't work without write permission in blob SAS to override an existing blob @loki", async () => { + it("Synchronized copy blob shouldn't work without write permission in blob SAS to override an existing blob @loki @sql", async () => { const now = new Date(); now.setMinutes(now.getMinutes() - 5); // Skip clock skew with server @@ -1356,7 +1362,7 @@ describe("Shared Access Signature (SAS) authentication", () => { assert.ok(error !== undefined); }); - it("Synchronized copy blob should work without write permission in account SAS to an nonexisting blob @loki", async () => { + it("Synchronized copy blob should work without write permission in account SAS to an nonexisting blob @loki @sql", async () => { const now = new Date(); now.setMinutes(now.getMinutes() - 5); // Skip clock skew with server @@ -1888,7 +1894,7 @@ describe("Shared Access Signature (SAS) authentication", () => { const sourceBlob = sourceContainerClient.getBlockBlobClient(blobName); await sourceBlob.upload("hello", 5); - sourceBlob.setAccessTier("Archive"); + await sourceBlob.setAccessTier("Archive"); const targetBlob = targetContainerClient.getBlockBlobClient(blobName); @@ -1903,7 +1909,7 @@ describe("Shared Access Signature (SAS) authentication", () => { assert.equal(error.details.code, "BlobArchived"); }); - it("Sync Copy blob across accounts should work and honor metadata when provided @loki", async () => { + it("Sync Copy blob across accounts should work and honor metadata when provided @loki @sql", async () => { const now = new Date(); now.setMinutes(now.getMinutes() - 5); // Skip clock skew with server diff --git a/tests/blob/specialnaming.test.ts b/tests/blob/specialnaming.test.ts index 24a6be889..1976e4744 100644 --- a/tests/blob/specialnaming.test.ts +++ b/tests/blob/specialnaming.test.ts @@ -445,7 +445,9 @@ describe("SpecialNaming", () => { assert.notDeepEqual(response.segment.blobItems.length, 0); }); - it(`Should work with production style URL when ${productionStyleHostName} is resolvable`, async () => { + // This now doesn't work (Error: Unable to extract accountName with provided information.). + // As the sdk expect an url in the form devstoreaccount1.blob.localhost instead of devstoreaccount1.localhost + it.skip(`Should work with production style URL when ${productionStyleHostName} is resolvable`, async () => { await dns.promises.lookup(productionStyleHostName).then( async (lookupAddress) => { const baseURLProductionStyle = `http://${productionStyleHostName}:${server.config.port}`; diff --git a/tests/blob/utils.test.ts b/tests/blob/utils.test.ts index c0d897148..a60c5d38e 100644 --- a/tests/blob/utils.test.ts +++ b/tests/blob/utils.test.ts @@ -2,7 +2,7 @@ import assert = require("assert"); import { convertRawHeadersToMetadata } from "../../src/common/utils/utils"; describe("Utils", () => { - it("convertRawHeadersToMetadata should work", () => { + it("convertRawHeadersToMetadata should work @loki @sql", () => { // upper case, lower case keys/values const metadata = convertRawHeadersToMetadata([ "x-ms-meta-Name1", @@ -22,7 +22,7 @@ describe("Utils", () => { }); }); - it("convertRawHeadersToMetadata should work with duplicated metadata", () => { + it("convertRawHeadersToMetadata should work with duplicated metadata @loki @sql", () => { const metadata = convertRawHeadersToMetadata([ "x-ms-meta-name1", "Value", @@ -34,7 +34,7 @@ describe("Utils", () => { }); }); - it("convertRawHeadersToMetadata should work with empty metadata", () => { + it("convertRawHeadersToMetadata should work with empty metadata @loki @sql", () => { const metadata = convertRawHeadersToMetadata([ "x-ms-meta-Name1", "", @@ -47,12 +47,12 @@ describe("Utils", () => { }); }); - it("convertRawHeadersToMetadata should work with empty raw headers", () => { + it("convertRawHeadersToMetadata should work with empty raw headers @loki @sql", () => { const metadata = convertRawHeadersToMetadata(); assert.deepStrictEqual(metadata, undefined); }); - it("convertRawHeadersToMetadata should work with empty raw headers array", () => { + it("convertRawHeadersToMetadata should work with empty raw headers array @loki @sql", () => { const metadata = convertRawHeadersToMetadata([]); assert.deepStrictEqual(metadata, undefined); });