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  }