github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/prxs3.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 "encoding/xml" 9 "errors" 10 "fmt" 11 "net/http" 12 "net/url" 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/debug" 21 "github.com/NVIDIA/aistore/cmn/mono" 22 "github.com/NVIDIA/aistore/cmn/nlog" 23 "github.com/NVIDIA/aistore/core/meta" 24 "github.com/NVIDIA/aistore/stats" 25 jsoniter "github.com/json-iterator/go" 26 ) 27 28 // TODO: `checkAccess` permissions (see ais/proxy.go) 29 30 var ( 31 errS3Req = errors.New("invalid s3 request") 32 errS3Obj = errors.New("missing or empty object name") 33 ) 34 35 // [METHOD] /s3 36 func (p *proxy) s3Handler(w http.ResponseWriter, r *http.Request) { 37 if cmn.Rom.FastV(5, cos.SmoduleS3) { 38 nlog.Infoln("s3Handler", p.String(), r.Method, r.URL) 39 } 40 41 // TODO: Fix the hack, https://github.com/tensorflow/tensorflow/issues/41798 42 cos.ReparseQuery(r) 43 apiItems, err := p.parseURL(w, r, apc.URLPathS3.L, 0, true) 44 if err != nil { 45 return 46 } 47 48 switch r.Method { 49 case http.MethodHead: 50 if len(apiItems) == 0 { 51 s3.WriteErr(w, r, errS3Req, 0) 52 return 53 } 54 if len(apiItems) == 1 { 55 p.headBckS3(w, r, apiItems[0]) 56 return 57 } 58 p.headObjS3(w, r, apiItems) 59 case http.MethodGet: 60 if len(apiItems) == 0 { 61 // list all buckets; NOTE: compare with `p.easyURLHandler` and see 62 // "list buckets for a given provider" comment there 63 p.bckNamesFromBMD(w) 64 return 65 } 66 var ( 67 q = r.URL.Query() 68 _, lifecycle = q[s3.QparamLifecycle] 69 _, policy = q[s3.QparamPolicy] 70 _, cors = q[s3.QparamCORS] 71 _, acl = q[s3.QparamACL] 72 ) 73 if lifecycle || policy || cors || acl { 74 p.unsupported(w, r, apiItems[0]) 75 return 76 } 77 listMultipart := q.Has(s3.QparamMptUploads) 78 if len(apiItems) == 1 && !listMultipart { 79 _, versioning := q[s3.QparamVersioning] 80 if versioning { 81 p.getBckVersioningS3(w, r, apiItems[0]) 82 return 83 } 84 p.listObjectsS3(w, r, apiItems[0], q) 85 return 86 } 87 // object data otherwise 88 p.getObjS3(w, r, apiItems, q, listMultipart) 89 case http.MethodPut: 90 if len(apiItems) == 0 { 91 s3.WriteErr(w, r, errS3Req, 0) 92 return 93 } 94 if len(apiItems) == 1 { 95 q := r.URL.Query() 96 _, versioning := q[s3.QparamVersioning] 97 if versioning { 98 p.putBckVersioningS3(w, r, apiItems[0]) 99 return 100 } 101 p.putBckS3(w, r, apiItems[0]) 102 return 103 } 104 p.putObjS3(w, r, apiItems) 105 case http.MethodPost: 106 q := r.URL.Query() 107 if q.Has(s3.QparamMptUploadID) || q.Has(s3.QparamMptUploads) { 108 p.handleMptUpload(w, r, apiItems) 109 return 110 } 111 if len(apiItems) != 1 { 112 s3.WriteErr(w, r, errS3Req, 0) 113 return 114 } 115 if _, multiple := q[s3.QparamMultiDelete]; !multiple { 116 s3.WriteErr(w, r, errS3Req, 0) 117 return 118 } 119 p.delMultipleObjs(w, r, apiItems[0]) 120 case http.MethodDelete: 121 if len(apiItems) == 0 { 122 s3.WriteErr(w, r, errS3Req, 0) 123 return 124 } 125 if len(apiItems) == 1 { 126 q := r.URL.Query() 127 _, multiple := q[s3.QparamMultiDelete] 128 if multiple { 129 p.delMultipleObjs(w, r, apiItems[0]) 130 return 131 } 132 p.delBckS3(w, r, apiItems[0]) 133 return 134 } 135 p.delObjS3(w, r, apiItems) 136 default: 137 cmn.WriteErr405(w, r, http.MethodDelete, http.MethodGet, http.MethodHead, 138 http.MethodPost, http.MethodPut) 139 } 140 } 141 142 // GET /s3 143 // NOTE: unlike native API, this one is limited to list only those that are currently present in the BMD. 144 func (p *proxy) bckNamesFromBMD(w http.ResponseWriter) { 145 var ( 146 bmd = p.owner.bmd.get() 147 resp = s3.NewListBucketResult() // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html 148 ) 149 bmd.Range(nil /*any provider*/, nil /*any namespace*/, func(bck *meta.Bck) bool { 150 resp.Add(bck) 151 return false 152 }) 153 sgl := p.gmm.NewSGL(0) 154 resp.MustMarshal(sgl) 155 w.Header().Set(cos.HdrContentType, cos.ContentXML) 156 sgl.WriteTo2(w) 157 sgl.Free() 158 } 159 160 // PUT /s3/<bucket-name> (i.e., create bucket) 161 func (p *proxy) putBckS3(w http.ResponseWriter, r *http.Request, bucket string) { 162 msg := apc.ActMsg{Action: apc.ActCreateBck} 163 if p.forwardCP(w, r, nil, msg.Action+"-"+bucket) { 164 return 165 } 166 bck := meta.NewBck(bucket, apc.AIS, cmn.NsGlobal) 167 if err := bck.Validate(); err != nil { 168 s3.WriteErr(w, r, err, 0) 169 return 170 } 171 if err := p.createBucket(&msg, bck, nil); err != nil { 172 s3.WriteErr(w, r, err, crerrStatus(err)) 173 } 174 } 175 176 // DELETE /s3/<bucket-name> (TODO: AWS allows to delete bucket only if it is empty) 177 func (p *proxy) delBckS3(w http.ResponseWriter, r *http.Request, bucket string) { 178 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 179 if err != nil { 180 s3.WriteErr(w, r, err, ecode) 181 return 182 } 183 if err := bck.Allow(apc.AceDestroyBucket); err != nil { 184 s3.WriteErr(w, r, err, http.StatusForbidden) 185 return 186 } 187 msg := apc.ActMsg{Action: apc.ActDestroyBck} 188 if p.forwardCP(w, r, nil, msg.Action+"-"+bucket) { 189 return 190 } 191 if err := p.destroyBucket(&msg, bck); err != nil { 192 ecode := http.StatusInternalServerError 193 if _, ok := err.(*cmn.ErrBucketAlreadyExists); ok { 194 nlog.Infof("%s: %s already %q-ed, nothing to do", p, bck, msg.Action) 195 return 196 } 197 s3.WriteErr(w, r, err, ecode) 198 } 199 } 200 201 func (p *proxy) handleMptUpload(w http.ResponseWriter, r *http.Request, parts []string) { 202 bucket := parts[0] 203 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 204 if err != nil { 205 s3.WriteErr(w, r, err, ecode) 206 return 207 } 208 if err := bck.Allow(apc.AcePUT); err != nil { 209 s3.WriteErr(w, r, err, http.StatusForbidden) 210 return 211 } 212 smap := p.owner.smap.get() 213 objName := s3.ObjName(parts) 214 if err := cmn.ValidateObjName(objName); err != nil { 215 s3.WriteErr(w, r, err, 0) 216 return 217 } 218 si, netPub, err := smap.HrwMultiHome(bck.MakeUname(objName)) 219 if err != nil { 220 s3.WriteErr(w, r, err, 0) 221 return 222 } 223 started := time.Now() 224 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraData, netPub) 225 p.s3Redirect(w, r, si, redirectURL, bck.Name) 226 } 227 228 // DELETE /s3/i<bucket-name>?delete 229 // Delete a list of objects 230 func (p *proxy) delMultipleObjs(w http.ResponseWriter, r *http.Request, bucket string) { 231 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 232 if err != nil { 233 s3.WriteErr(w, r, err, ecode) 234 return 235 } 236 if err := bck.Allow(apc.AceObjDELETE); err != nil { 237 s3.WriteErr(w, r, err, http.StatusForbidden) 238 return 239 } 240 decoder := xml.NewDecoder(r.Body) 241 objList := &s3.Delete{} 242 if err := decoder.Decode(objList); err != nil { 243 s3.WriteErr(w, r, err, 0) 244 return 245 } 246 if len(objList.Object) == 0 { 247 return 248 } 249 250 var ( 251 msg = apc.ActMsg{Action: apc.ActDeleteObjects} 252 lrMsg = &apc.ListRange{ObjNames: make([]string, 0, len(objList.Object))} 253 ) 254 for _, obj := range objList.Object { 255 lrMsg.ObjNames = append(lrMsg.ObjNames, obj.Key) 256 } 257 msg.Value = lrMsg 258 259 // marshal+unmarshal to convince `p.listrange` to treat `listMsg` as `map[string]interface` 260 var ( 261 msg2 apc.ActMsg 262 bt = cos.MustMarshal(&msg) 263 query = make(url.Values, 1) 264 ) 265 query.Set(apc.QparamProvider, apc.AIS) 266 if err := jsoniter.Unmarshal(bt, &msg2); err != nil { 267 err = fmt.Errorf(cmn.FmtErrUnmarshal, p, "list-range action message", cos.BHead(bt), err) 268 s3.WriteErr(w, r, err, 0) 269 return 270 } 271 if _, err := p.listrange(http.MethodDelete, bucket, &msg2, query); err != nil { 272 s3.WriteErr(w, r, err, 0) 273 } 274 // TODO: The client wants the response containing two lists: 275 // - Successfully deleted objects 276 // - Failed delete calls with error message. 277 // AIS targets do not track this info. They report a single result: 278 // whether there were any errors while deleting objects. 279 // So, we fill only "Deleted successfully" response part. 280 // See: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html 281 all := &s3.DeleteResult{Objs: make([]s3.DeletedObjInfo, 0, len(lrMsg.ObjNames))} 282 for _, name := range lrMsg.ObjNames { 283 all.Objs = append(all.Objs, s3.DeletedObjInfo{Key: name}) 284 } 285 sgl := p.gmm.NewSGL(0) 286 all.MustMarshal(sgl) 287 w.Header().Set(cos.HdrContentType, cos.ContentXML) 288 sgl.WriteTo2(w) 289 sgl.Free() 290 } 291 292 // HEAD /s3/<bucket-name> 293 func (p *proxy) headBckS3(w http.ResponseWriter, r *http.Request, bucket string) { 294 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 295 if err != nil { 296 s3.WriteErr(w, r, err, ecode) 297 return 298 } 299 if err := bck.Allow(apc.AceBckHEAD); err != nil { 300 s3.WriteErr(w, r, err, http.StatusForbidden) 301 return 302 } 303 // From https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html: 304 // 305 // "This operation is useful to determine if a bucket exists and you have 306 // permission to access it. The operation returns a 200 OK if the bucket 307 // exists and you have permission to access it. Otherwise, the operation 308 // might return responses such as 404 Not Found and 403 Forbidden." 309 // 310 // But it appears that Amazon always adds region to the response, 311 // and AWS CLI uses it. 312 w.Header().Set(cos.HdrServer, s3.AISServer) 313 w.Header().Set(cos.S3HdrBckRegion, s3.AISRegion) 314 } 315 316 // GET /s3/<bucket-name> 317 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html 318 func (p *proxy) listObjectsS3(w http.ResponseWriter, r *http.Request, bucket string, q url.Values) { 319 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 320 if err != nil { 321 s3.WriteErr(w, r, err, ecode) 322 return 323 } 324 amsg := &apc.ActMsg{Action: apc.ActList} 325 326 // currently, always forwarding 327 if p.forwardCP(w, r, amsg, lsotag+" "+bck.String()) { 328 return 329 } 330 331 // e.g. <LastModified>2009-10-12T17:50:30.000Z</LastModified> 332 lsmsg := &apc.LsoMsg{TimeFormat: cos.ISO8601} 333 334 // NOTE: hard-coded props as per FromLsoResult (see below) 335 lsmsg.AddProps(apc.GetPropsSize, apc.GetPropsChecksum, apc.GetPropsAtime) 336 amsg.Value = lsmsg 337 338 // as per API_ListObjectsV2.html, optional: 339 // - "max-keys" 340 // - "prefix" 341 // - "start-after" 342 // - "delimiter" (TODO: limited support: no recursion) 343 // - "continuation-token" (NOTE: base64 encoded, as in: base64.StdEncoding.DecodeString(token) 344 // TODO: 345 // - "fetch-owner" 346 // - "encoding-type" 347 s3.FillLsoMsg(q, lsmsg) 348 349 lst, err := p.lsAllPagesS3(bck, amsg, lsmsg, r.Header) 350 if cmn.Rom.FastV(5, cos.SmoduleS3) { 351 nlog.Infoln("lsoS3", bck.Cname(""), len(lst.Entries), err) 352 } 353 if err != nil { 354 s3.WriteErr(w, r, err, 0) 355 return 356 } 357 358 // NOTE: 359 // - the following few lines of code translate (using additional memory) list-objects 360 // results into S3 format, and then use xml encoding to serialize the entire thing; 361 // - compare with native Go-based API that utilizes message pack encoding with 362 // (certainly) no translations; 363 // - the implication: if, when working with very large remote datasets, list-objects performance 364 // becomes an issue - consider using native API. 365 366 resp := s3.NewListObjectResult(bucket) 367 resp.ContinuationToken = lsmsg.ContinuationToken 368 resp.FromLsoResult(lst, lsmsg) 369 sgl := p.gmm.NewSGL(0) 370 resp.MustMarshal(sgl) 371 w.Header().Set(cos.HdrContentType, cos.ContentXML) 372 sgl.WriteTo2(w) 373 sgl.Free() 374 375 // GC 376 clear(lst.Entries) 377 lst.Entries = lst.Entries[:0] 378 lst.Entries = nil 379 lst = nil 380 } 381 382 func (p *proxy) lsAllPagesS3(bck *meta.Bck, amsg *apc.ActMsg, lsmsg *apc.LsoMsg, hdr http.Header) (lst *cmn.LsoRes, _ error) { 383 smap := p.owner.smap.get() 384 for pageNum := 1; ; pageNum++ { 385 beg := mono.NanoTime() 386 page, err := p.lsPage(bck, amsg, lsmsg, hdr, smap) 387 if err != nil { 388 return lst, err 389 } 390 p.statsT.AddMany( 391 cos.NamedVal64{Name: stats.ListCount, Value: 1}, 392 cos.NamedVal64{Name: stats.ListLatency, Value: mono.SinceNano(beg)}, 393 ) 394 if pageNum == 1 { 395 lst = page 396 lsmsg.UUID = page.UUID 397 debug.Assert(cos.IsValidUUID(lst.UUID), lst.UUID) 398 } else { 399 lst.Entries = append(lst.Entries, page.Entries...) 400 lst.ContinuationToken = page.ContinuationToken 401 debug.Assert(lst.UUID == page.UUID, lst.UUID, page.UUID) 402 lst.Flags |= page.Flags 403 } 404 if page.ContinuationToken == "" { // listed all pages 405 break 406 } 407 lsmsg.ContinuationToken = page.ContinuationToken 408 amsg.Value = lsmsg 409 } 410 return lst, nil 411 } 412 413 // PUT /s3/<bucket-name>/<object-name> 414 func (p *proxy) putObjS3(w http.ResponseWriter, r *http.Request, items []string) { 415 if r.Header.Get(cos.S3HdrObjSrc) == "" { 416 p.directPutObjS3(w, r, items) 417 return 418 } 419 p.copyObjS3(w, r, items) 420 } 421 422 // PUT /s3/<bucket-name>/<object-name> - with HeaderObjSrc in the request header 423 // (compare with p.directPutObjS3) 424 func (p *proxy) copyObjS3(w http.ResponseWriter, r *http.Request, items []string) { 425 src := r.Header.Get(cos.S3HdrObjSrc) 426 src = strings.Trim(src, "/") 427 parts := strings.SplitN(src, "/", 2) 428 if len(parts) < 2 { 429 s3.WriteErr(w, r, errS3Obj, 0) 430 return 431 } 432 // src 433 bckSrc, err, ecode := meta.InitByNameOnly(parts[0], p.owner.bmd) 434 if err != nil { 435 s3.WriteErr(w, r, err, ecode) 436 return 437 } 438 if err := bckSrc.Allow(apc.AceGET); err != nil { 439 s3.WriteErr(w, r, err, http.StatusForbidden) 440 return 441 } 442 // dst 443 bckDst, err, ecode := meta.InitByNameOnly(items[0], p.owner.bmd) 444 if err != nil { 445 s3.WriteErr(w, r, err, ecode) 446 return 447 } 448 var ( 449 si *meta.Snode 450 smap = p.owner.smap.get() 451 ) 452 if err = bckDst.Allow(apc.AcePUT); err != nil { 453 s3.WriteErr(w, r, err, http.StatusForbidden) 454 return 455 } 456 objName := strings.Trim(parts[1], "/") 457 si, err = smap.HrwName2T(bckSrc.MakeUname(objName)) 458 if err != nil { 459 s3.WriteErr(w, r, err, 0) 460 return 461 } 462 if cmn.Rom.FastV(5, cos.SmoduleS3) { 463 nlog.Infof("COPY: %s %s => %s/%v %s", r.Method, bckSrc.Cname(objName), bckDst.Cname(""), items, si) 464 } 465 started := time.Now() 466 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraControl) 467 p.s3Redirect(w, r, si, redirectURL, bckDst.Name) 468 } 469 470 // PUT /s3/<bucket-name>/<object-name> - with empty `cos.S3HdrObjSrc` 471 // (compare with p.copyObjS3) 472 func (p *proxy) directPutObjS3(w http.ResponseWriter, r *http.Request, items []string) { 473 bucket := items[0] 474 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 475 if err != nil { 476 s3.WriteErr(w, r, err, ecode) 477 return 478 } 479 var ( 480 netPub string 481 si *meta.Snode 482 smap = p.owner.smap.get() 483 ) 484 if err = bck.Allow(apc.AcePUT); err != nil { 485 s3.WriteErr(w, r, err, http.StatusForbidden) 486 return 487 } 488 if len(items) < 2 { 489 s3.WriteErr(w, r, errS3Obj, 0) 490 return 491 } 492 objName := s3.ObjName(items) 493 if err := cmn.ValidateObjName(objName); err != nil { 494 s3.WriteErr(w, r, err, 0) 495 return 496 } 497 si, netPub, err = smap.HrwMultiHome(bck.MakeUname(objName)) 498 if err != nil { 499 s3.WriteErr(w, r, err, 0) 500 return 501 } 502 if cmn.Rom.FastV(5, cos.SmoduleS3) { 503 nlog.Infof("%s %s => %s", r.Method, bck.Cname(objName), si) 504 } 505 started := time.Now() 506 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraData, netPub) 507 p.s3Redirect(w, r, si, redirectURL, bck.Name) 508 } 509 510 // GET /s3/<bucket-name>/<object-name> 511 func (p *proxy) getObjS3(w http.ResponseWriter, r *http.Request, items []string, q url.Values, listMultipart bool) { 512 bucket := items[0] 513 514 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 515 if err != nil { 516 s3.WriteErr(w, r, err, ecode) 517 return 518 } 519 var ( 520 si *meta.Snode 521 netPub string 522 smap = p.owner.smap.get() 523 ) 524 if err = bck.Allow(apc.AceGET); err != nil { 525 s3.WriteErr(w, r, err, http.StatusForbidden) 526 return 527 } 528 if listMultipart { 529 p.listMultipart(w, r, bck, q) 530 return 531 } 532 if len(items) < 2 { 533 s3.WriteErr(w, r, errS3Obj, 0) 534 return 535 } 536 objName := s3.ObjName(items) 537 if err := cmn.ValidateObjName(objName); err != nil { 538 s3.WriteErr(w, r, err, 0) 539 return 540 } 541 si, netPub, err = smap.HrwMultiHome(bck.MakeUname(objName)) 542 if err != nil { 543 s3.WriteErr(w, r, err, 0) 544 return 545 } 546 if cmn.Rom.FastV(5, cos.SmoduleS3) { 547 nlog.Infof("%s %s => %s", r.Method, bck.Cname(objName), si) 548 } 549 started := time.Now() 550 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraData, netPub) 551 p.s3Redirect(w, r, si, redirectURL, bck.Name) 552 } 553 554 // GET /s3/<bucket-name>/<object-name> with `s3.QparamMptUploads` 555 func (p *proxy) listMultipart(w http.ResponseWriter, r *http.Request, bck *meta.Bck, q url.Values) { 556 smap := p.owner.smap.get() 557 if smap.CountActiveTs() == 1 { 558 si, err := smap.HrwName2T(bck.MakeUname("")) 559 if err != nil { 560 s3.WriteErr(w, r, err, 0) 561 return 562 } 563 started := time.Now() 564 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraControl) 565 p.s3Redirect(w, r, si, redirectURL, bck.Name) 566 return 567 } 568 // bcast & aggregate 569 all := &s3.ListMptUploadsResult{} 570 for _, si := range smap.Tmap { 571 var ( 572 url = si.URL(cmn.NetPublic) 573 cargs = allocCargs() 574 ) 575 cargs.si = si 576 cargs.req = cmn.HreqArgs{Method: http.MethodGet, Base: url, Path: r.URL.Path, Query: q} 577 res := p.call(cargs, smap) 578 b, err := res.bytes, res.err 579 freeCargs(cargs) 580 freeCR(res) 581 if err == nil { 582 results := &s3.ListMptUploadsResult{} 583 if err := xml.Unmarshal(b, results); err == nil { 584 if len(results.Uploads) > 0 { 585 if len(all.Uploads) == 0 { 586 *all = *results 587 all.Uploads = make([]s3.UploadInfoResult, 0) 588 } 589 all.Uploads = append(all.Uploads, results.Uploads...) 590 } 591 } 592 } 593 } 594 sgl := p.gmm.NewSGL(0) 595 all.MustMarshal(sgl) 596 w.Header().Set(cos.HdrContentType, cos.ContentXML) 597 sgl.WriteTo2(w) 598 sgl.Free() 599 } 600 601 // HEAD /s3/<bucket-name>/<object-name> 602 func (p *proxy) headObjS3(w http.ResponseWriter, r *http.Request, items []string) { 603 if len(items) < 2 { 604 s3.WriteErr(w, r, errS3Obj, 0) 605 return 606 } 607 bucket, objName := items[0], s3.ObjName(items) 608 if err := cmn.ValidateObjName(objName); err != nil { 609 s3.WriteErr(w, r, err, 0) 610 return 611 } 612 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 613 if err != nil { 614 s3.WriteErr(w, r, err, ecode) 615 return 616 } 617 if err := bck.Allow(apc.AceObjHEAD); err != nil { 618 s3.WriteErr(w, r, err, http.StatusForbidden) 619 return 620 } 621 smap := p.owner.smap.get() 622 si, err := smap.HrwName2T(bck.MakeUname(objName)) 623 if err != nil { 624 s3.WriteErr(w, r, err, http.StatusInternalServerError) 625 return 626 } 627 if cmn.Rom.FastV(5, cos.SmoduleS3) { 628 nlog.Infof("%s %s => %s", r.Method, bck.Cname(objName), si) 629 } 630 631 p.reverseNodeRequest(w, r, si) 632 } 633 634 // DELETE /s3/<bucket-name>/<object-name> 635 func (p *proxy) delObjS3(w http.ResponseWriter, r *http.Request, items []string) { 636 bucket := items[0] 637 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 638 if err != nil { 639 s3.WriteErr(w, r, err, ecode) 640 return 641 } 642 var ( 643 si *meta.Snode 644 smap = p.owner.smap.get() 645 ) 646 if err = bck.Allow(apc.AceObjDELETE); err != nil { 647 s3.WriteErr(w, r, err, http.StatusForbidden) 648 return 649 } 650 if len(items) < 2 { 651 s3.WriteErr(w, r, errS3Obj, 0) 652 return 653 } 654 objName := s3.ObjName(items) 655 if err := cmn.ValidateObjName(objName); err != nil { 656 s3.WriteErr(w, r, err, 0) 657 return 658 } 659 si, err = smap.HrwName2T(bck.MakeUname(objName)) 660 if err != nil { 661 s3.WriteErr(w, r, err, 0) 662 return 663 } 664 if cmn.Rom.FastV(5, cos.SmoduleS3) { 665 nlog.Infof("%s %s => %s", r.Method, bck.Cname(objName), si) 666 } 667 started := time.Now() 668 redirectURL := p.redirectURL(r, si, started, cmn.NetIntraControl) 669 p.s3Redirect(w, r, si, redirectURL, bck.Name) 670 } 671 672 // GET /s3/<bucket-name>?versioning 673 func (p *proxy) getBckVersioningS3(w http.ResponseWriter, r *http.Request, bucket string) { 674 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 675 if err != nil { 676 s3.WriteErr(w, r, err, ecode) 677 return 678 } 679 resp := s3.NewVersioningConfiguration(bck.Props.Versioning.Enabled) 680 sgl := p.gmm.NewSGL(0) 681 resp.MustMarshal(sgl) 682 w.Header().Set(cos.HdrContentType, cos.ContentXML) 683 sgl.WriteTo2(w) 684 sgl.Free() 685 } 686 687 // GET /s3/<bucket-name>?lifecycle|cors|policy|acl 688 func (p *proxy) unsupported(w http.ResponseWriter, r *http.Request, bucket string) { 689 if _, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd); err != nil { 690 s3.WriteErr(w, r, err, ecode) 691 return 692 } 693 w.WriteHeader(http.StatusNotImplemented) 694 } 695 696 // PUT /s3/<bucket-name>?versioning 697 func (p *proxy) putBckVersioningS3(w http.ResponseWriter, r *http.Request, bucket string) { 698 msg := &apc.ActMsg{Action: apc.ActSetBprops} 699 if p.forwardCP(w, r, nil, msg.Action+"-"+bucket) { 700 return 701 } 702 bck, err, ecode := meta.InitByNameOnly(bucket, p.owner.bmd) 703 if err != nil { 704 s3.WriteErr(w, r, err, ecode) 705 return 706 } 707 decoder := xml.NewDecoder(r.Body) 708 vconf := &s3.VersioningConfiguration{} 709 if err := decoder.Decode(vconf); err != nil { 710 s3.WriteErr(w, r, err, 0) 711 return 712 } 713 enabled := vconf.Enabled() 714 propsToUpdate := cmn.BpropsToSet{ 715 Versioning: &cmn.VersionConfToSet{Enabled: &enabled}, 716 } 717 // make and validate new props 718 nprops, err := p.makeNewBckProps(bck, &propsToUpdate) 719 if err != nil { 720 s3.WriteErr(w, r, err, 0) 721 return 722 } 723 if _, err := p.setBprops(msg, bck, nprops); err != nil { 724 s3.WriteErr(w, r, err, 0) 725 } 726 }