azurite
Version:
An open source Azure Storage API compatible server
289 lines • 14.5 kB
JavaScript
"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