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  }