import IExtentStore from "../../common/persistence/IExtentStore" ; import { convertRawHeadersToMetadata , newEtag } from "../../common/utils/utils" ; import BlobStorageContext from "../context/BlobStorageContext" ; import NotImplementedError from "../errors/NotImplementedError" ; import StorageErrorFactory from "../errors/StorageErrorFactory" ; import * as Models from "../generated/artifacts/models" ; import Context from "../generated/Context" ; import IPageBlobHandler from "../generated/handlers/IPageBlobHandler" ; import ILogger from "../generated/utils/ILogger" ; import BlobLeaseAdapter from "../lease/BlobLeaseAdapter" ; import BlobWriteLeaseValidator from "../lease/BlobWriteLeaseValidator" ; import IBlobMetadataStore , { BlobModel } from "../persistence/IBlobMetadataStore" ; import { BLOB_API_VERSION } from "../utils/constants" ; import { deserializePageBlobRangeHeader } from "../utils/utils" ; import BaseHandler from "./BaseHandler" ; import IPageBlobRangesManager from "./IPageBlobRangesManager" ; export class extends BaseHandler implements IPageBlobHandler export default class PageBlobHandler extends BaseHandler implements IPageBlobHandler { constructor ( metadataStore , extentStore , logger , loose , private readonly rangesManager ) { super ( metadataStore , extentStore , logger , loose ) ; } public async uploadPagesFromURL ( sourceUrl , sourceRange , contentLength , range , options , context ) { throw new NotImplementedError ( context . contextId ) ; } public async create ( contentLength , blobContentLength , options , context ) { const blobCtx = new BlobStorageContext ( context ) ; const accountName = blobCtx . account ! ; const containerName = blobCtx . container ! ; const blobName = blobCtx . blob ! ; const date = blobCtx . startTime ! ; if ( options . pageBlobAccessTier !== undefined ) { throw StorageErrorFactory . getAccessTierNotSupportedForBlobType ( context . contextId ! ) ; } if ( contentLength !== 0 ) { throw StorageErrorFactory . getInvalidOperation ( blobCtx . contextId ! , "Content-Length must be 0 for Create Page Blob request." ) ; } if ( blobContentLength % 512 !== 0 ) { throw StorageErrorFactory . 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 metadata = convertRawHeadersToMetadata ( blobCtx . request ! . getRawHeaders ( ) ) ; const etag = 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 } , snapshot : "" , isCommitted : true , pageRangesInOrder : [ ] } ; 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 : BLOB_API_VERSION , date , isServerEncrypted : true , clientRequestId : options . requestId } ; return response ; } public async uploadPages ( body , contentLength , options , context ) { const blobCtx = new BlobStorageContext ( context ) ; const accountName = blobCtx . account ! ; const containerName = blobCtx . container ! ; const blobName = blobCtx . blob ! ; const date = blobCtx . startTime ! ; if ( contentLength % 512 !== 0 ) { throw StorageErrorFactory . 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 . getBlobInvalidBlobType ( blobCtx . contextId ! ) ; } new BlobWriteLeaseValidator ( options . leaseAccessConditions ) . validate ( new BlobLeaseAdapter ( blob ) , context ) ; let ranges ; try { ranges = deserializePageBlobRangeHeader ( blobCtx . request ! . getHeader ( "range" ) , blobCtx . request ! . getHeader ( "x-ms-range" ) , true ) ; } catch ( err ) { throw StorageErrorFactory . getInvalidPageRange ( blobCtx . contextId ! ) ; } const start = ranges [ 0 ] ; const end = ranges [ 1 ] ; if ( end - start + 1 !== contentLength ) { throw StorageErrorFactory . getInvalidPageRange ( blobCtx . contextId ! ) ; } if ( start >= blob . properties . contentLength ! ) { throw StorageErrorFactory . getInvalidPageRange ( blobCtx . contextId ! ) ; } const persistency = await this . extentStore . appendExtent ( body , context . contextId ) ; if ( persistency . count !== contentLength ) { throw StorageErrorFactory . 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 , blobSequenceNumber : res . blobSequenceNumber , requestId : blobCtx . contextId , version : BLOB_API_VERSION , date , isServerEncrypted : true , clientRequestId : options . requestId } ; return response ; } public async clearPages ( contentLength , options , context ) { const blobCtx = new BlobStorageContext ( context ) ; const accountName = blobCtx . account ! ; const containerName = blobCtx . container ! ; const blobName = blobCtx . blob ! ; const date = blobCtx . startTime ! ; if ( contentLength !== 0 ) { throw StorageErrorFactory . 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 . getBlobInvalidBlobType ( blobCtx . contextId ! ) ; } let ranges ; try { ranges = deserializePageBlobRangeHeader ( blobCtx . request ! . getHeader ( "range" ) , blobCtx . request ! . getHeader ( "x-ms-range" ) , true ) ; } catch ( err ) { throw StorageErrorFactory . getInvalidPageRange ( blobCtx . contextId ! ) ; } const start = ranges [ 0 ] ; const end = ranges [ 1 ] ; if ( start >= blob . properties . contentLength ! ) { throw StorageErrorFactory . 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 , blobSequenceNumber : res . blobSequenceNumber , requestId : blobCtx . contextId , version : BLOB_API_VERSION , clientRequestId : options . requestId , date } ; return response ; } public async getPageRanges ( options , context ) { const blobCtx = new BlobStorageContext ( 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 . getBlobInvalidBlobType ( blobCtx . contextId ! ) ; } let ranges = deserializePageBlobRangeHeader ( blobCtx . request ! . getHeader ( "range" ) , blobCtx . request ! . getHeader ( "x-ms-range" ) , false ) ; if ( ! ranges ) { ranges = [ 0 , blob . properties . contentLength ! - 1 ] ; } if ( ranges [ 0 ] >= blob . properties . contentLength ! ) { throw StorageErrorFactory . 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 : BLOB_API_VERSION , clientRequestId : options . requestId , date } ; return response ; } public async getPageRangesDiff ( prevsnapshot , options , context ) { throw new NotImplementedError ( context . contextId ) ; } public async resize ( blobContentLength , options , context ) { const blobCtx = new BlobStorageContext ( context ) ; const accountName = blobCtx . account ! ; const containerName = blobCtx . container ! ; const blobName = blobCtx . blob ! ; const date = blobCtx . startTime ! ; if ( blobContentLength % 512 !== 0 ) { throw StorageErrorFactory . 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 : BLOB_API_VERSION , clientRequestId : options . requestId , date } ; return response ; } public async updateSequenceNumber ( sequenceNumberAction , options , context ) { const blobCtx = new BlobStorageContext ( 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 : BLOB_API_VERSION , clientRequestId : options . requestId , date } ; return response ; } public async copyIncremental ( copySource , options , context ) { throw new NotImplementedError ( context . contextId ) ; } }