github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/tgts3.go (about) 1 // Package ais provides core functionality for the AIStore object storage. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package ais 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "net/http" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/NVIDIA/aistore/ais/s3" 17 "github.com/NVIDIA/aistore/api/apc" 18 "github.com/NVIDIA/aistore/cmn" 19 "github.com/NVIDIA/aistore/cmn/cos" 20 "github.com/NVIDIA/aistore/cmn/feat" 21 "github.com/NVIDIA/aistore/cmn/nlog" 22 "github.com/NVIDIA/aistore/core" 23 "github.com/NVIDIA/aistore/core/meta" 24 "github.com/NVIDIA/aistore/ec" 25 "github.com/NVIDIA/aistore/fs" 26 ) 27 28 const fmtErrBckObj = "invalid %s request: expecting bucket and object (names) in the URL, have %v" 29 30 // [METHOD] /s3 31 func (t *target) s3Handler(w http.ResponseWriter, r *http.Request) { 32 if cmn.Rom.FastV(5, cos.SmoduleS3) { 33 nlog.Infoln("s3Handler", t.String(), r.Method, r.URL) 34 } 35 apiItems, err := t.parseURL(w, r, apc.URLPathS3.L, 0, true) 36 if err != nil { 37 return 38 } 39 if l := len(apiItems); (l == 0 && r.Method == http.MethodGet) || l < 2 { 40 err := fmt.Errorf(fmtErrBckObj, r.Method, apiItems) 41 s3.WriteErr(w, r, err, 0) 42 return 43 } 44 45 switch r.Method { 46 case http.MethodHead: 47 t.headObjS3(w, r, apiItems) 48 case http.MethodGet: 49 t.getObjS3(w, r, apiItems) 50 case http.MethodPut: 51 config := cmn.GCO.Get() 52 t.putCopyMpt(w, r, config, apiItems) 53 case http.MethodDelete: 54 q := r.URL.Query() 55 if q.Has(s3.QparamMptUploadID) { 56 t.abortMpt(w, r, apiItems, q) 57 } else { 58 t.delObjS3(w, r, apiItems) 59 } 60 case http.MethodPost: 61 t.postObjS3(w, r, apiItems) 62 default: 63 cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPost) 64 } 65 } 66 67 // PUT /s3/<bucket-name>/<object-name> 68 // [switch] mpt | put | copy 69 func (t *target) putCopyMpt(w http.ResponseWriter, r *http.Request, config *cmn.Config, items []string) { 70 cs := fs.Cap() 71 if cs.IsOOS() { 72 s3.WriteErr(w, r, cs.Err(), http.StatusInsufficientStorage) 73 return 74 } 75 bck, err, ecode := meta.InitByNameOnly(items[0], t.owner.bmd) 76 if err != nil { 77 s3.WriteErr(w, r, err, ecode) 78 return 79 } 80 q := r.URL.Query() 81 switch { 82 case q.Has(s3.QparamMptPartNo) && q.Has(s3.QparamMptUploadID): 83 if r.Header.Get(cos.S3HdrObjSrc) != "" { 84 // TODO: copy another object (or its range) => part of the specified multipart upload. 85 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPartCopy.html 86 s3.WriteErr(w, r, errors.New("UploadPartCopy not implemented yet"), http.StatusNotImplemented) 87 return 88 } 89 if cmn.Rom.FastV(5, cos.SmoduleS3) { 90 nlog.Infoln("putMptPart", bck.String(), items, q) 91 } 92 t.putMptPart(w, r, items, q, bck) 93 case r.Header.Get(cos.S3HdrObjSrc) == "": 94 objName := s3.ObjName(items) 95 lom := core.AllocLOM(objName) 96 t.putObjS3(w, r, bck, config, lom) 97 core.FreeLOM(lom) 98 default: 99 t.copyObjS3(w, r, config, items) 100 } 101 } 102 103 // Copy object (maybe from another bucket) 104 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html 105 func (t *target) copyObjS3(w http.ResponseWriter, r *http.Request, config *cmn.Config, items []string) { 106 src := r.Header.Get(cos.S3HdrObjSrc) 107 src = strings.Trim(src, "/") // in AWS examples the path starts with "/" 108 parts := strings.SplitN(src, "/", 2) 109 if len(parts) < 2 { 110 s3.WriteErr(w, r, errS3Obj, 0) 111 return 112 } 113 // src 114 bckSrc, err, ecode := meta.InitByNameOnly(parts[0], t.owner.bmd) 115 if err != nil { 116 s3.WriteErr(w, r, err, ecode) 117 return 118 } 119 objSrc := strings.Trim(parts[1], "/") 120 if err := bckSrc.Init(t.owner.bmd); err != nil { 121 s3.WriteErr(w, r, err, 0) 122 return 123 } 124 lom := core.AllocLOM(objSrc) 125 defer core.FreeLOM(lom) 126 if err := lom.InitBck(bckSrc.Bucket()); err != nil { 127 if cmn.IsErrRemoteBckNotFound(err) { 128 t.BMDVersionFixup(r) 129 err = lom.InitBck(bckSrc.Bucket()) 130 } 131 if err != nil { 132 s3.WriteErr(w, r, err, 0) 133 } 134 return 135 } 136 if err := lom.Load(false /*cache it*/, false /*locked*/); err != nil { 137 s3.WriteErr(w, r, err, 0) 138 return 139 } 140 // dst 141 bckTo, err, ecode := meta.InitByNameOnly(items[0], t.owner.bmd) 142 if err != nil { 143 s3.WriteErr(w, r, err, ecode) 144 return 145 } 146 147 coiParams := core.AllocCOI() 148 { 149 coiParams.Config = config 150 coiParams.BckTo = bckTo 151 coiParams.ObjnameTo = s3.ObjName(items) 152 coiParams.OWT = cmn.OwtCopy 153 } 154 coi := (*copyOI)(coiParams) 155 _, err = coi.do(t, nil /*DM*/, lom) 156 core.FreeCOI(coiParams) 157 158 if err != nil { 159 if err == cmn.ErrSkip { 160 name := lom.Cname() 161 s3.WriteErr(w, r, cos.NewErrNotFound(t, name), http.StatusNotFound) 162 } else { 163 s3.WriteErr(w, r, err, 0) 164 } 165 return 166 } 167 168 var cksumValue string 169 if cksum := lom.Checksum(); cksum.Type() == cos.ChecksumMD5 { 170 cksumValue = cksum.Value() 171 } 172 result := s3.CopyObjectResult{ 173 LastModified: cos.FormatNanoTime(lom.AtimeUnix(), cos.ISO8601), 174 ETag: cksumValue, 175 } 176 sgl := t.gmm.NewSGL(0) 177 result.MustMarshal(sgl) 178 w.Header().Set(cos.HdrContentType, cos.ContentXML) 179 sgl.WriteTo2(w) 180 sgl.Free() 181 } 182 183 func (t *target) putObjS3(w http.ResponseWriter, r *http.Request, bck *meta.Bck, config *cmn.Config, lom *core.LOM) { 184 if err := lom.InitBck(bck.Bucket()); err != nil { 185 if cmn.IsErrRemoteBckNotFound(err) { 186 t.BMDVersionFixup(r) 187 err = lom.InitBck(bck.Bucket()) 188 } 189 if err != nil { 190 s3.WriteErr(w, r, err, 0) 191 return 192 } 193 } 194 started := time.Now() 195 lom.SetAtimeUnix(started.UnixNano()) 196 197 // TODO: dual checksumming, e.g. lom.SetCustom(apc.AWS, ...) 198 199 dpq := dpqAlloc() 200 defer dpqFree(dpq) 201 if err := dpq.parse(r.URL.RawQuery); err != nil { 202 s3.WriteErr(w, r, err, 0) 203 return 204 } 205 poi := allocPOI() 206 { 207 poi.atime = started.UnixNano() 208 poi.t = t 209 poi.lom = lom 210 poi.config = config 211 poi.skipVC = cmn.Rom.Features().IsSet(feat.SkipVC) || dpq.skipVC // apc.QparamSkipVC 212 poi.restful = true 213 } 214 ecode, err := poi.do(nil /*response hdr*/, r, dpq) 215 freePOI(poi) 216 if err != nil { 217 t.fsErr(err, lom.FQN) 218 s3.WriteErr(w, r, err, ecode) 219 return 220 } 221 s3.SetEtag(w.Header(), lom) 222 } 223 224 // GET s3/<bucket-name[/<object-name>] 225 func (t *target) getObjS3(w http.ResponseWriter, r *http.Request, items []string) { 226 bucket := items[0] 227 bck, err, ecode := meta.InitByNameOnly(bucket, t.owner.bmd) 228 if err != nil { 229 s3.WriteErr(w, r, err, ecode) 230 return 231 } 232 q := r.URL.Query() 233 if len(items) == 1 && q.Has(s3.QparamMptUploads) { 234 if cmn.Rom.FastV(5, cos.SmoduleS3) { 235 nlog.Infoln("listMptUploads", bck.String(), q) 236 } 237 t.listMptUploads(w, bck, q) 238 return 239 } 240 if len(items) < 2 { 241 err := fmt.Errorf(fmtErrBckObj, r.Method, items) 242 s3.WriteErr(w, r, err, 0) 243 return 244 } 245 objName := s3.ObjName(items) 246 if q.Has(s3.QparamMptPartNo) { 247 if cmn.Rom.FastV(5, cos.SmoduleS3) { 248 nlog.Infoln("getMptPart", bck.String(), objName, q) 249 } 250 t.getMptPart(w, r, bck, objName, q) 251 return 252 } 253 uploadID := q.Get(s3.QparamMptUploadID) 254 if uploadID != "" { 255 if cmn.Rom.FastV(5, cos.SmoduleS3) { 256 nlog.Infoln("listMptParts", bck.String(), objName, q) 257 } 258 t.listMptParts(w, r, bck, objName, q) 259 return 260 } 261 262 dpq := dpqAlloc() 263 if err := dpq.parse(r.URL.RawQuery); err != nil { 264 dpqFree(dpq) 265 s3.WriteErr(w, r, err, 0) 266 return 267 } 268 lom := core.AllocLOM(objName) 269 dpq.isS3 = true 270 lom, err = t.getObject(w, r, dpq, bck, lom) 271 core.FreeLOM(lom) 272 273 if err != nil { 274 s3.WriteErr(w, r, err, 0) 275 } 276 dpqFree(dpq) 277 } 278 279 // HEAD /s3/<bucket-name>/<object-name> (TODO: s3.HdrMptCnt) 280 // See: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html 281 func (t *target) headObjS3(w http.ResponseWriter, r *http.Request, items []string) { 282 bucket, objName := items[0], s3.ObjName(items) 283 bck, err, ecode := meta.InitByNameOnly(bucket, t.owner.bmd) 284 if err != nil { 285 s3.WriteErr(w, r, err, ecode) 286 return 287 } 288 lom := core.AllocLOM(objName) 289 defer core.FreeLOM(lom) 290 if err := lom.InitBck(bck.Bucket()); err != nil { 291 s3.WriteErr(w, r, err, 0) 292 return 293 } 294 exists := true 295 err = lom.Load(true /*cache it*/, false /*locked*/) 296 if err != nil { 297 exists = false 298 if !cos.IsNotExist(err, 0) { 299 s3.WriteErr(w, r, err, 0) 300 return 301 } 302 if bck.IsAIS() { 303 s3.WriteErr(w, r, cos.NewErrNotFound(t, lom.Cname()), 0) 304 return 305 } 306 } 307 308 var ( 309 hdr = w.Header() 310 op cmn.ObjectProps 311 ) 312 if exists { 313 op.ObjAttrs = *lom.ObjAttrs() 314 } else { 315 // cold HEAD 316 objAttrs, ecode, err := t.Backend(lom.Bck()).HeadObj(context.Background(), lom, r) 317 if err != nil { 318 s3.WriteErr(w, r, err, ecode) 319 return 320 } 321 op.ObjAttrs = *objAttrs 322 } 323 324 custom := op.GetCustomMD() 325 lom.SetCustomMD(custom) 326 if v, ok := custom[cos.HdrETag]; ok { 327 hdr.Set(cos.HdrETag, v) 328 } 329 s3.SetEtag(hdr, lom) 330 hdr.Set(cos.HdrContentLength, strconv.FormatInt(op.Size, 10)) 331 if v, ok := custom[cos.HdrContentType]; ok { 332 hdr.Set(cos.HdrContentType, v) 333 } 334 // e.g. https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_Examples 335 // (compare w/ `p.listObjectsS3()` 336 lastModified := cos.FormatNanoTime(op.Atime, cos.RFC1123GMT) 337 hdr.Set(cos.S3LastModified, lastModified) 338 339 // TODO: lom.Checksum() via apc.HeaderPrefix+apc.HdrObjCksumType/Val via 340 // s3 obj Metadata map[string]*string 341 } 342 343 // DELETE /s3/<bucket-name>/<object-name> 344 func (t *target) delObjS3(w http.ResponseWriter, r *http.Request, items []string) { 345 bck, err, ecode := meta.InitByNameOnly(items[0], t.owner.bmd) 346 if err != nil { 347 s3.WriteErr(w, r, err, ecode) 348 return 349 } 350 objName := s3.ObjName(items) 351 lom := core.AllocLOM(objName) 352 defer core.FreeLOM(lom) 353 if err := lom.InitBck(bck.Bucket()); err != nil { 354 s3.WriteErr(w, r, err, 0) 355 return 356 } 357 ecode, err = t.DeleteObject(lom, false) 358 if err != nil { 359 name := lom.Cname() 360 if ecode == http.StatusNotFound { 361 s3.WriteErr(w, r, cos.NewErrNotFound(t, name), http.StatusNotFound) 362 } else { 363 s3.WriteErr(w, r, fmt.Errorf("error deleting %s: %v", name, err), ecode) 364 } 365 return 366 } 367 // EC cleanup if EC is enabled 368 ec.ECM.CleanupObject(lom) 369 } 370 371 // POST /s3/<bucket-name>/<object-name> 372 func (t *target) postObjS3(w http.ResponseWriter, r *http.Request, items []string) { 373 bck, err, ecode := meta.InitByNameOnly(items[0], t.owner.bmd) 374 if err != nil { 375 s3.WriteErr(w, r, err, ecode) 376 return 377 } 378 q := r.URL.Query() 379 if q.Has(s3.QparamMptUploads) { 380 if cmn.Rom.FastV(5, cos.SmoduleS3) { 381 nlog.Infoln("startMpt", bck.String(), items, q) 382 } 383 t.startMpt(w, r, items, bck, q) 384 return 385 } 386 if q.Has(s3.QparamMptUploadID) { 387 if cmn.Rom.FastV(5, cos.SmoduleS3) { 388 nlog.Infoln("completeMpt", bck.String(), items, q) 389 } 390 t.completeMpt(w, r, items, q, bck) 391 return 392 } 393 err = fmt.Errorf("set query parameter %q to start multipart upload or %q to complete the upload", 394 s3.QparamMptUploads, s3.QparamMptUploadID) 395 s3.WriteErr(w, r, err, 0) 396 }