github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/storage/driver/azure/randomwriter.go (about) 1 package azure 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 8 azure "github.com/Azure/azure-sdk-for-go/storage" 9 ) 10 11 // blockStorage is the interface required from a block storage service 12 // client implementation 13 type blockStorage interface { 14 CreateBlockBlob(container, blob string) error 15 GetBlob(container, blob string) (io.ReadCloser, error) 16 GetSectionReader(container, blob string, start, length int64) (io.ReadCloser, error) 17 PutBlock(container, blob, blockID string, chunk []byte) error 18 GetBlockList(container, blob string, blockType azure.BlockListType) (azure.BlockListResponse, error) 19 PutBlockList(container, blob string, blocks []azure.Block) error 20 } 21 22 // randomBlobWriter enables random access semantics on Azure block blobs 23 // by enabling writing arbitrary length of chunks to arbitrary write offsets 24 // within the blob. Normally, Azure Blob Storage does not support random 25 // access semantics on block blobs; however, this writer can download, split and 26 // reupload the overlapping blocks and discards those being overwritten entirely. 27 type randomBlobWriter struct { 28 bs blockStorage 29 blockSize int 30 } 31 32 func newRandomBlobWriter(bs blockStorage, blockSize int) randomBlobWriter { 33 return randomBlobWriter{bs: bs, blockSize: blockSize} 34 } 35 36 // WriteBlobAt writes the given chunk to the specified position of an existing blob. 37 // The offset must be equals to size of the blob or smaller than it. 38 func (r *randomBlobWriter) WriteBlobAt(container, blob string, offset int64, chunk io.Reader) (int64, error) { 39 rand := newBlockIDGenerator() 40 41 blocks, err := r.bs.GetBlockList(container, blob, azure.BlockListTypeCommitted) 42 if err != nil { 43 return 0, err 44 } 45 rand.Feed(blocks) // load existing block IDs 46 47 // Check for write offset for existing blob 48 size := getBlobSize(blocks) 49 if offset < 0 || offset > size { 50 return 0, fmt.Errorf("wrong offset for Write: %v", offset) 51 } 52 53 // Upload the new chunk as blocks 54 blockList, nn, err := r.writeChunkToBlocks(container, blob, chunk, rand) 55 if err != nil { 56 return 0, err 57 } 58 59 // For non-append operations, existing blocks may need to be splitted 60 if offset != size { 61 // Split the block on the left end (if any) 62 leftBlocks, err := r.blocksLeftSide(container, blob, offset, rand) 63 if err != nil { 64 return 0, err 65 } 66 blockList = append(leftBlocks, blockList...) 67 68 // Split the block on the right end (if any) 69 rightBlocks, err := r.blocksRightSide(container, blob, offset, nn, rand) 70 if err != nil { 71 return 0, err 72 } 73 blockList = append(blockList, rightBlocks...) 74 } else { 75 // Use existing block list 76 var existingBlocks []azure.Block 77 for _, v := range blocks.CommittedBlocks { 78 existingBlocks = append(existingBlocks, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted}) 79 } 80 blockList = append(existingBlocks, blockList...) 81 } 82 // Put block list 83 return nn, r.bs.PutBlockList(container, blob, blockList) 84 } 85 86 func (r *randomBlobWriter) GetSize(container, blob string) (int64, error) { 87 blocks, err := r.bs.GetBlockList(container, blob, azure.BlockListTypeCommitted) 88 if err != nil { 89 return 0, err 90 } 91 return getBlobSize(blocks), nil 92 } 93 94 // writeChunkToBlocks writes given chunk to one or multiple blocks within specified 95 // blob and returns their block representations. Those blocks are not committed, yet 96 func (r *randomBlobWriter) writeChunkToBlocks(container, blob string, chunk io.Reader, rand *blockIDGenerator) ([]azure.Block, int64, error) { 97 var newBlocks []azure.Block 98 var nn int64 99 100 // Read chunks of at most size N except the last chunk to 101 // maximize block size and minimize block count. 102 buf := make([]byte, r.blockSize) 103 for { 104 n, err := io.ReadFull(chunk, buf) 105 if err == io.EOF { 106 break 107 } 108 nn += int64(n) 109 data := buf[:n] 110 blockID := rand.Generate() 111 if err := r.bs.PutBlock(container, blob, blockID, data); err != nil { 112 return newBlocks, nn, err 113 } 114 newBlocks = append(newBlocks, azure.Block{ID: blockID, Status: azure.BlockStatusUncommitted}) 115 } 116 return newBlocks, nn, nil 117 } 118 119 // blocksLeftSide returns the blocks that are going to be at the left side of 120 // the writeOffset: [0, writeOffset) by identifying blocks that will remain 121 // the same and splitting blocks and reuploading them as needed. 122 func (r *randomBlobWriter) blocksLeftSide(container, blob string, writeOffset int64, rand *blockIDGenerator) ([]azure.Block, error) { 123 var left []azure.Block 124 bx, err := r.bs.GetBlockList(container, blob, azure.BlockListTypeAll) 125 if err != nil { 126 return left, err 127 } 128 129 o := writeOffset 130 elapsed := int64(0) 131 for _, v := range bx.CommittedBlocks { 132 blkSize := int64(v.Size) 133 if o >= blkSize { // use existing block 134 left = append(left, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted}) 135 o -= blkSize 136 elapsed += blkSize 137 } else if o > 0 { // current block needs to be splitted 138 start := elapsed 139 size := o 140 part, err := r.bs.GetSectionReader(container, blob, start, size) 141 if err != nil { 142 return left, err 143 } 144 newBlockID := rand.Generate() 145 146 data, err := ioutil.ReadAll(part) 147 if err != nil { 148 return left, err 149 } 150 if err = r.bs.PutBlock(container, blob, newBlockID, data); err != nil { 151 return left, err 152 } 153 left = append(left, azure.Block{ID: newBlockID, Status: azure.BlockStatusUncommitted}) 154 break 155 } 156 } 157 return left, nil 158 } 159 160 // blocksRightSide returns the blocks that are going to be at the right side of 161 // the written chunk: [writeOffset+size, +inf) by identifying blocks that will remain 162 // the same and splitting blocks and reuploading them as needed. 163 func (r *randomBlobWriter) blocksRightSide(container, blob string, writeOffset int64, chunkSize int64, rand *blockIDGenerator) ([]azure.Block, error) { 164 var right []azure.Block 165 166 bx, err := r.bs.GetBlockList(container, blob, azure.BlockListTypeAll) 167 if err != nil { 168 return nil, err 169 } 170 171 re := writeOffset + chunkSize - 1 // right end of written chunk 172 var elapsed int64 173 for _, v := range bx.CommittedBlocks { 174 var ( 175 bs = elapsed // left end of current block 176 be = elapsed + int64(v.Size) - 1 // right end of current block 177 ) 178 179 if bs > re { // take the block as is 180 right = append(right, azure.Block{ID: v.Name, Status: azure.BlockStatusCommitted}) 181 } else if be > re { // current block needs to be splitted 182 part, err := r.bs.GetSectionReader(container, blob, re+1, be-(re+1)+1) 183 if err != nil { 184 return right, err 185 } 186 newBlockID := rand.Generate() 187 188 data, err := ioutil.ReadAll(part) 189 if err != nil { 190 return right, err 191 } 192 if err = r.bs.PutBlock(container, blob, newBlockID, data); err != nil { 193 return right, err 194 } 195 right = append(right, azure.Block{ID: newBlockID, Status: azure.BlockStatusUncommitted}) 196 } 197 elapsed += int64(v.Size) 198 } 199 return right, nil 200 } 201 202 func getBlobSize(blocks azure.BlockListResponse) int64 { 203 var n int64 204 for _, v := range blocks.CommittedBlocks { 205 n += int64(v.Size) 206 } 207 return n 208 }