UNPKG

azurite

Version:

An open source Azure Storage API compatible server

289 lines 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const utils_1 = require("../../common/utils/utils"); const BlobStorageContext_1 = tslib_1.__importDefault(require("../context/BlobStorageContext")); const NotImplementedError_1 = tslib_1.__importDefault(require("../errors/NotImplementedError")); const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory")); const Models = tslib_1.__importStar(require("../generated/artifacts/models")); const BlobLeaseAdapter_1 = tslib_1.__importDefault(require("../lease/BlobLeaseAdapter")); const BlobWriteLeaseValidator_1 = tslib_1.__importDefault(require("../lease/BlobWriteLeaseValidator")); const constants_1 = require("../utils/constants"); const utils_2 = require("../utils/utils"); const BaseHandler_1 = tslib_1.__importDefault(require("./BaseHandler")); /** * PageBlobHandler handles Azure Storage PageBlob related requests. * * @export * @class PageBlobHandler * @extends {BaseHandler} * @implements {IPageBlobHandler} */ class PageBlobHandler extends BaseHandler_1.default { constructor(metadataStore, extentStore, logger, loose, rangesManager) { super(metadataStore, extentStore, logger, loose); this.rangesManager = rangesManager; } async uploadPagesFromURL(sourceUrl, sourceRange, contentLength, range, options, context) { throw new NotImplementedError_1.default(context.contextId); } async create(contentLength, blobContentLength, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; if (options.tier !== undefined) { throw StorageErrorFactory_1.default.getAccessTierNotSupportedForBlobType(context.contextId); } if (contentLength !== 0) { throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, "Content-Length must be 0 for Create Page Blob request."); } if (blobContentLength % 512 !== 0) { throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, "x-ms-content-length must be aligned to a 512-byte boundary."); } options.blobHTTPHeaders = options.blobHTTPHeaders || {}; const contentType = options.blobHTTPHeaders.blobContentType || context.request.getHeader("content-type") || "application/octet-stream"; // const accessTierInferred = options.pageBlobAccessTier === undefined; // Check Blob size match tier // if ( // !accessTierInferred && // blobContentLength > PageBlobAccessTierThreshold.get(tier)! // ) { // throw StorageErrorFactory.getBlobBlobTierInadequateForContentLength( // blobCtx.contextID! // ); // } // Preserve metadata key case const metadata = (0, utils_1.convertRawHeadersToMetadata)(blobCtx.request.getRawHeaders(), context.contextId); const etag = (0, utils_1.newEtag)(); const blob = { deleted: false, metadata, accountName, containerName, name: blobName, properties: { creationTime: date, lastModified: date, etag, contentLength: blobContentLength, contentType, contentEncoding: options.blobHTTPHeaders.blobContentEncoding, contentLanguage: options.blobHTTPHeaders.blobContentLanguage, contentMD5: options.blobHTTPHeaders.blobContentMD5, contentDisposition: options.blobHTTPHeaders.blobContentDisposition, cacheControl: options.blobHTTPHeaders.blobCacheControl, blobSequenceNumber: options.blobSequenceNumber ? options.blobSequenceNumber : 0, blobType: Models.BlobType.PageBlob, leaseStatus: Models.LeaseStatusType.Unlocked, leaseState: Models.LeaseStateType.Available, serverEncrypted: true // TODO: May support setting this part for a premium storage account. // accessTier: accessTierInferred // ? ((options.pageBlobAccessTier as any) as Models.AccessTier) // : Models.AccessTier.P4, // TODO: Infer tier from size // accessTierInferred }, snapshot: "", isCommitted: true, pageRangesInOrder: [], blobTags: options.blobTagsString === undefined ? undefined : (0, utils_2.getTagsFromString)(options.blobTagsString, context.contextId), }; // TODO: What's happens when create page blob right before commit block list? Or should we lock // Should we check if there is an uncommitted blob? await this.metadataStore.createBlob(context, blob, options.leaseAccessConditions, options.modifiedAccessConditions); const response = { statusCode: 201, eTag: etag, lastModified: blob.properties.lastModified, contentMD5: blob.properties.contentMD5, requestId: context.contextId, version: constants_1.BLOB_API_VERSION, date, isServerEncrypted: true, clientRequestId: options.requestId }; return response; } async uploadPages(body, contentLength, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; if (contentLength % 512 !== 0) { throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, "content-length or x-ms-content-length must be aligned to a 512-byte boundary."); } const blob = await this.metadataStore.downloadBlob(context, accountName, containerName, blobName, undefined, options.leaseAccessConditions); if (blob.properties.blobType !== Models.BlobType.PageBlob) { throw StorageErrorFactory_1.default.getBlobInvalidBlobType(blobCtx.contextId); } // Check Lease status new BlobWriteLeaseValidator_1.default(options.leaseAccessConditions).validate(new BlobLeaseAdapter_1.default(blob), context); let ranges; try { ranges = (0, utils_2.deserializePageBlobRangeHeader)(blobCtx.request.getHeader("range"), blobCtx.request.getHeader("x-ms-range"), true); } catch (err) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } const start = ranges[0]; const end = ranges[1]; // Inclusive if (end - start + 1 !== contentLength) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } // Start Range is bigger than blob length if (start >= blob.properties.contentLength) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } const persistency = await this.extentStore.appendExtent(body, context.contextId); if (persistency.count !== contentLength) { // TODO: Confirm status code throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, `The size of the request body ${persistency.count} mismatches the content-length ${contentLength}.`); } const res = await this.metadataStore.uploadPages(context, blob, start, end, persistency, options.leaseAccessConditions, options.modifiedAccessConditions, options.sequenceNumberAccessConditions); const response = { statusCode: 201, eTag: res.etag, lastModified: date, contentMD5: undefined, // TODO blobSequenceNumber: res.blobSequenceNumber, requestId: blobCtx.contextId, version: constants_1.BLOB_API_VERSION, date, isServerEncrypted: true, clientRequestId: options.requestId }; return response; } async clearPages(contentLength, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; if (contentLength !== 0) { throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, "content-length or x-ms-content-length must be 0 for clear pages operation."); } const blob = await this.metadataStore.downloadBlob(context, accountName, containerName, blobName, undefined, options.leaseAccessConditions); if (blob.properties.blobType !== Models.BlobType.PageBlob) { throw StorageErrorFactory_1.default.getBlobInvalidBlobType(blobCtx.contextId); } let ranges; try { ranges = (0, utils_2.deserializePageBlobRangeHeader)(blobCtx.request.getHeader("range"), blobCtx.request.getHeader("x-ms-range"), true); } catch (err) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } const start = ranges[0]; const end = ranges[1]; // Start Range is bigger than blob length if (start >= blob.properties.contentLength) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } const res = await this.metadataStore.clearRange(context, blob, start, end, options.leaseAccessConditions, options.modifiedAccessConditions, options.sequenceNumberAccessConditions); const response = { statusCode: 201, eTag: res.etag, lastModified: date, contentMD5: undefined, // TODO blobSequenceNumber: res.blobSequenceNumber, requestId: blobCtx.contextId, version: constants_1.BLOB_API_VERSION, clientRequestId: options.requestId, date }; return response; } async getPageRanges(options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; const blob = await this.metadataStore.getPageRanges(context, accountName, containerName, blobName, options.snapshot, options.leaseAccessConditions, options.modifiedAccessConditions); if (blob.properties.blobType !== Models.BlobType.PageBlob) { throw StorageErrorFactory_1.default.getBlobInvalidBlobType(blobCtx.contextId); } let ranges = (0, utils_2.deserializePageBlobRangeHeader)(blobCtx.request.getHeader("range"), blobCtx.request.getHeader("x-ms-range"), false); if (!ranges) { ranges = [0, blob.properties.contentLength - 1]; } // Start Range is bigger than blob length if (ranges[0] >= blob.properties.contentLength) { throw StorageErrorFactory_1.default.getInvalidPageRange(blobCtx.contextId); } blob.pageRangesInOrder = blob.pageRangesInOrder || []; const impactedRanges = this.rangesManager.cutRanges(blob.pageRangesInOrder, { start: ranges[0], end: ranges[1] }); const response = { statusCode: 200, pageRange: impactedRanges, eTag: blob.properties.etag, blobContentLength: blob.properties.contentLength, lastModified: date, requestId: blobCtx.contextId, version: constants_1.BLOB_API_VERSION, clientRequestId: options.requestId, date }; return response; } async getPageRangesDiff(options, context) { throw new NotImplementedError_1.default(context.contextId); } async resize(blobContentLength, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; if (blobContentLength % 512 !== 0) { throw StorageErrorFactory_1.default.getInvalidOperation(blobCtx.contextId, "x-ms-blob-content-length must be aligned to a 512-byte boundary for Page Blob Resize request."); } const res = await this.metadataStore.resizePageBlob(context, accountName, containerName, blobName, blobContentLength, options.leaseAccessConditions, options.modifiedAccessConditions); const response = { statusCode: 200, eTag: res.etag, lastModified: res.lastModified, blobSequenceNumber: res.blobSequenceNumber, requestId: blobCtx.contextId, version: constants_1.BLOB_API_VERSION, clientRequestId: options.requestId, date }; return response; } async updateSequenceNumber(sequenceNumberAction, options, context) { const blobCtx = new BlobStorageContext_1.default(context); const accountName = blobCtx.account; const containerName = blobCtx.container; const blobName = blobCtx.blob; const date = blobCtx.startTime; const res = await this.metadataStore.updateSequenceNumber(context, accountName, containerName, blobName, sequenceNumberAction, options.blobSequenceNumber, options.leaseAccessConditions, options.modifiedAccessConditions); const response = { statusCode: 200, eTag: res.etag, lastModified: res.lastModified, blobSequenceNumber: res.blobSequenceNumber, requestId: blobCtx.contextId, version: constants_1.BLOB_API_VERSION, clientRequestId: options.requestId, date }; return response; } async copyIncremental(copySource, options, context) { throw new NotImplementedError_1.default(context.contextId); } } exports.default = PageBlobHandler; //# sourceMappingURL=PageBlobHandler.js.map