github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/s3/mpt.go (about) 1 // Package s3 provides Amazon S3 compatibility layer 2 /* 3 * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package s3 6 7 import ( 8 "fmt" 9 "net/http" 10 "os" 11 "sort" 12 "strconv" 13 "sync" 14 "time" 15 16 "github.com/NVIDIA/aistore/cmn/debug" 17 "github.com/NVIDIA/aistore/cmn/nlog" 18 "github.com/NVIDIA/aistore/core" 19 ) 20 21 // NOTE: xattr stores only the (*) marked attributes 22 type ( 23 MptPart struct { 24 MD5 string // MD5 of the part (*) 25 FQN string // FQN of the corresponding workfile 26 Size int64 // part size in bytes (*) 27 Num int32 // part number (*) 28 } 29 mpt struct { 30 bckName string 31 objName string 32 parts []*MptPart // by part number 33 ctime time.Time // InitUpload time 34 } 35 uploads map[string]*mpt // by upload ID 36 ) 37 38 var ( 39 ups uploads 40 mu sync.RWMutex 41 ) 42 43 // Start miltipart upload 44 func InitUpload(id, bckName, objName string) { 45 mu.Lock() 46 if ups == nil { 47 ups = make(uploads, 8) 48 } 49 ups[id] = &mpt{ 50 bckName: bckName, 51 objName: objName, 52 parts: make([]*MptPart, 0, iniCapParts), 53 ctime: time.Now(), 54 } 55 mu.Unlock() 56 } 57 58 // Add part to an active upload. 59 // Some clients may omit size and md5. Only partNum is must-have. 60 // md5 and fqn is filled by a target after successful saving the data to a workfile. 61 func AddPart(id string, npart *MptPart) (err error) { 62 mu.Lock() 63 mpt, ok := ups[id] 64 if !ok { 65 err = fmt.Errorf("upload %q not found (%s, %d)", id, npart.FQN, npart.Num) 66 } else { 67 mpt.parts = append(mpt.parts, npart) 68 } 69 mu.Unlock() 70 return 71 } 72 73 // TODO: compare non-zero sizes (note: s3cmd sends 0) and part.ETag as well, if specified 74 func CheckParts(id string, parts []*PartInfo) ([]*MptPart, error) { 75 mu.RLock() 76 defer mu.RUnlock() 77 mpt, ok := ups[id] 78 if !ok { 79 return nil, fmt.Errorf("upload %q not found", id) 80 } 81 // first, check that all parts are present 82 var prev = int32(-1) 83 for _, part := range parts { 84 debug.Assert(part.PartNumber > prev) // must ascend 85 if mpt.getPart(part.PartNumber) == nil { 86 return nil, fmt.Errorf("upload %q: part %d not found", id, part.PartNumber) 87 } 88 prev = part.PartNumber 89 } 90 // copy (to work on it with no locks) 91 nparts := make([]*MptPart, 0, len(parts)) 92 for _, part := range parts { 93 nparts = append(nparts, mpt.getPart(part.PartNumber)) 94 } 95 return nparts, nil 96 } 97 98 func ParsePartNum(s string) (int32, error) { 99 partNum, err := strconv.ParseInt(s, 10, 32) 100 if err != nil { 101 err = fmt.Errorf("invalid part number %q (must be in 1-%d range): %v", s, MaxPartsPerUpload, err) 102 } 103 return int32(partNum), err 104 } 105 106 // Return a sum of upload part sizes. 107 // Used on upload completion to calculate the final size of the object. 108 func ObjSize(id string) (size int64, err error) { 109 mu.RLock() 110 mpt, ok := ups[id] 111 if !ok { 112 err = fmt.Errorf("upload %q not found", id) 113 } else { 114 for _, part := range mpt.parts { 115 size += part.Size 116 } 117 } 118 mu.RUnlock() 119 return 120 } 121 122 // remove all temp files and delete from the map 123 // if completed (i.e., not aborted): store xattr 124 func CleanupUpload(id, fqn string, aborted bool) (exists bool) { 125 mu.Lock() 126 mpt, ok := ups[id] 127 if !ok { 128 mu.Unlock() 129 nlog.Warningf("fqn %s, id %s", fqn, id) 130 return false 131 } 132 delete(ups, id) 133 mu.Unlock() 134 135 if !aborted { 136 if err := storeMptXattr(fqn, mpt); err != nil { 137 nlog.Warningf("fqn %s, id %s: %v", fqn, id, err) 138 } 139 } 140 for _, part := range mpt.parts { 141 if err := os.Remove(part.FQN); err != nil && !os.IsNotExist(err) { 142 nlog.Errorln(err) 143 } 144 } 145 return true 146 } 147 148 func ListUploads(bckName, idMarker string, maxUploads int) (result *ListMptUploadsResult) { 149 mu.RLock() 150 results := make([]UploadInfoResult, 0, len(ups)) 151 for id, mpt := range ups { 152 results = append(results, UploadInfoResult{Key: mpt.objName, UploadID: id, Initiated: mpt.ctime}) 153 } 154 mu.RUnlock() 155 156 sort.Slice(results, func(i int, j int) bool { 157 return results[i].Initiated.Before(results[j].Initiated) 158 }) 159 160 var from int 161 if idMarker != "" { 162 // truncate 163 for i, res := range results { 164 if res.UploadID == idMarker { 165 from = i + 1 166 break 167 } 168 copy(results, results[from:]) 169 results = results[:len(results)-from] 170 } 171 } 172 if maxUploads > 0 && len(results) > maxUploads { 173 results = results[:maxUploads] 174 } 175 result = &ListMptUploadsResult{Bucket: bckName, Uploads: results, IsTruncated: from > 0} 176 return 177 } 178 179 func ListParts(id string, lom *core.LOM) (parts []*PartInfo, ecode int, err error) { 180 mu.RLock() 181 mpt, ok := ups[id] 182 if !ok { 183 ecode = http.StatusNotFound 184 mpt, err = loadMptXattr(lom.FQN) 185 if err != nil || mpt == nil { 186 mu.RUnlock() 187 return nil, ecode, err 188 } 189 mpt.bckName, mpt.objName = lom.Bck().Name, lom.ObjName 190 mpt.ctime = lom.Atime() 191 } 192 parts = make([]*PartInfo, 0, len(mpt.parts)) 193 for _, part := range mpt.parts { 194 parts = append(parts, &PartInfo{ETag: part.MD5, PartNumber: part.Num, Size: part.Size}) 195 } 196 mu.RUnlock() 197 return parts, ecode, err 198 }