github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/s3/types.go (about)

     1  // Package s3 provides Amazon S3 compatibility layer
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package s3
     6  
     7  import (
     8  	"encoding/xml"
     9  	"net/http"
    10  	"net/url"
    11  	"path"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/NVIDIA/aistore/api/apc"
    16  	"github.com/NVIDIA/aistore/cmn"
    17  	"github.com/NVIDIA/aistore/cmn/cos"
    18  	"github.com/NVIDIA/aistore/cmn/debug"
    19  	"github.com/NVIDIA/aistore/core"
    20  	"github.com/NVIDIA/aistore/memsys"
    21  )
    22  
    23  const defaultLastModified = 0 // When an object was not accessed yet
    24  
    25  // NOTE: do not rename structs that have `xml` tags. The names of those structs
    26  // become a top level tag of resulting XML, and those tags S3-compatible
    27  // clients require.
    28  type (
    29  	// List objects response
    30  	ListObjectResult struct {
    31  		Name                  string          `xml:"Name"`
    32  		Ns                    string          `xml:"xmlns,attr"`
    33  		Prefix                string          `xml:"Prefix"`
    34  		KeyCount              int             `xml:"KeyCount"`                 // number of object names in the response
    35  		MaxKeys               int             `xml:"MaxKeys"`                  // "The maximum number of keys returned ..." (s3)
    36  		IsTruncated           bool            `xml:"IsTruncated"`              // true if there are more pages to read
    37  		ContinuationToken     string          `xml:"ContinuationToken"`        // original ContinuationToken
    38  		NextContinuationToken string          `xml:"NextContinuationToken"`    // NextContinuationToken to read the next page
    39  		Contents              []*ObjInfo      `xml:"Contents"`                 // list of objects
    40  		CommonPrefixes        []*CommonPrefix `xml:"CommonPrefixes,omitempty"` // list of dirs (used with `apc.LsNoRecursion`)
    41  	}
    42  	ObjInfo struct {
    43  		Key          string `xml:"Key"`
    44  		LastModified string `xml:"LastModified"`
    45  		ETag         string `xml:"ETag"`
    46  		Size         int64  `xml:"Size"`
    47  		Class        string `xml:"StorageClass"`
    48  	}
    49  	CommonPrefix struct {
    50  		Prefix string `xml:"Prefix"`
    51  	}
    52  
    53  	// Response for object copy request
    54  	CopyObjectResult struct {
    55  		LastModified string `xml:"LastModified"` // e.g. <LastModified>2009-10-12T17:50:30.000Z</LastModified>
    56  		ETag         string `xml:"ETag"`
    57  	}
    58  
    59  	// Multipart upload start response
    60  	InitiateMptUploadResult struct {
    61  		Bucket   string `xml:"Bucket"`
    62  		Key      string `xml:"Key"`
    63  		UploadID string `xml:"UploadId"`
    64  	}
    65  
    66  	// Multipart uploaded part
    67  	PartInfo struct {
    68  		ETag       string `xml:"ETag"`
    69  		PartNumber int32  `xml:"PartNumber"`
    70  		Size       int64  `xml:"Size,omitempty"`
    71  	}
    72  
    73  	// Multipart upload completion request
    74  	CompleteMptUpload struct {
    75  		Parts []*PartInfo `xml:"Part"`
    76  	}
    77  
    78  	// Multipart upload completion response
    79  	CompleteMptUploadResult struct {
    80  		Bucket string `xml:"Bucket"`
    81  		Key    string `xml:"Key"`
    82  		ETag   string `xml:"ETag"`
    83  	}
    84  
    85  	// Multipart uploaded parts response
    86  	ListPartsResult struct {
    87  		Bucket   string      `xml:"Bucket"`
    88  		Key      string      `xml:"Key"`
    89  		UploadID string      `xml:"UploadId"`
    90  		Parts    []*PartInfo `xml:"Part"`
    91  	}
    92  
    93  	// Active upload info
    94  	UploadInfoResult struct {
    95  		Key       string    `xml:"Key"`
    96  		UploadID  string    `xml:"UploadId"`
    97  		Initiated time.Time `xml:"Initiated"`
    98  	}
    99  
   100  	// List of active multipart uploads response
   101  	ListMptUploadsResult struct {
   102  		Bucket         string             `xml:"Bucket"`
   103  		UploadIDMarker string             `xml:"UploadIdMarker"`
   104  		Uploads        []UploadInfoResult `xml:"Upload"`
   105  		MaxUploads     int
   106  		IsTruncated    bool
   107  	}
   108  
   109  	// Deleted result: list of deleted objects and errors
   110  	DeletedObjInfo struct {
   111  		Key string `xml:"Key"`
   112  	}
   113  	DeleteResult struct {
   114  		Objs []DeletedObjInfo `xml:"Deleted"`
   115  	}
   116  )
   117  
   118  func ObjName(items []string) string { return path.Join(items[1:]...) }
   119  
   120  func FillLsoMsg(query url.Values, msg *apc.LsoMsg) {
   121  	mxStr := query.Get(QparamMaxKeys)
   122  	if pageSize, err := strconv.Atoi(mxStr); err == nil && pageSize > 0 {
   123  		msg.PageSize = int64(pageSize)
   124  	}
   125  	if prefix := query.Get(QparamPrefix); prefix != "" {
   126  		msg.Prefix = prefix
   127  	}
   128  	var token string
   129  	if token = query.Get(QparamContinuationToken); token != "" {
   130  		// base64 encoded, as in: base64.StdEncoding.DecodeString(token)
   131  		msg.ContinuationToken = token
   132  	}
   133  	// `start-after` is used only when starting to list pages, subsequent next-page calls
   134  	// utilize `continuation-token`
   135  	if after := query.Get(QparamStartAfter); after != "" && token == "" {
   136  		msg.StartAfter = after
   137  	}
   138  	// TODO: check that the delimiter is '/' and raise an error otherwise
   139  	if delimiter := query.Get(QparamDelimiter); delimiter != "" {
   140  		msg.SetFlag(apc.LsNoRecursion)
   141  	}
   142  }
   143  
   144  func NewListObjectResult(bucket string) *ListObjectResult {
   145  	return &ListObjectResult{
   146  		Name:     bucket,
   147  		Ns:       s3Namespace,
   148  		MaxKeys:  1000,
   149  		Contents: make([]*ObjInfo, 0),
   150  	}
   151  }
   152  
   153  func (r *ListObjectResult) MustMarshal(sgl *memsys.SGL) {
   154  	sgl.Write([]byte(xml.Header))
   155  	err := xml.NewEncoder(sgl).Encode(r)
   156  	debug.AssertNoErr(err)
   157  }
   158  
   159  func (r *ListObjectResult) Add(entry *cmn.LsoEnt, lsmsg *apc.LsoMsg) {
   160  	if entry.Flags&apc.EntryIsDir == 0 {
   161  		r.Contents = append(r.Contents, entryToS3(entry, lsmsg))
   162  	} else {
   163  		r.CommonPrefixes = append(r.CommonPrefixes, &CommonPrefix{Prefix: entry.Name + "/"})
   164  	}
   165  }
   166  
   167  func entryToS3(entry *cmn.LsoEnt, lsmsg *apc.LsoMsg) *ObjInfo {
   168  	objInfo := &ObjInfo{
   169  		Key:          entry.Name,
   170  		LastModified: entry.Atime,
   171  		ETag:         entry.Checksum,
   172  		Size:         entry.Size,
   173  	}
   174  	// Some S3 clients do not tolerate empty or missing LastModified, so fill it
   175  	// with a zero time if the object was not accessed yet
   176  	if objInfo.LastModified == "" {
   177  		objInfo.LastModified = cos.FormatNanoTime(defaultLastModified, lsmsg.TimeFormat)
   178  	}
   179  	return objInfo
   180  }
   181  
   182  func (r *ListObjectResult) FromLsoResult(lst *cmn.LsoRes, lsmsg *apc.LsoMsg) {
   183  	r.KeyCount = len(lst.Entries)
   184  	r.IsTruncated = lst.ContinuationToken != ""
   185  	r.NextContinuationToken = lst.ContinuationToken
   186  	for _, e := range lst.Entries {
   187  		r.Add(e, lsmsg)
   188  	}
   189  }
   190  
   191  func SetEtag(hdr http.Header, lom *core.LOM) {
   192  	if hdr.Get(cos.S3CksumHeader) != "" {
   193  		return
   194  	}
   195  	if v, exists := lom.GetCustomKey(cmn.ETag); exists && !cmn.IsS3MultipartEtag(v) {
   196  		hdr.Set(cos.S3CksumHeader /*"ETag"*/, v)
   197  		return
   198  	}
   199  	if cksum := lom.Checksum(); cksum.Type() == cos.ChecksumMD5 {
   200  		hdr.Set(cos.S3CksumHeader, cksum.Value())
   201  	}
   202  }
   203  
   204  func (r *CopyObjectResult) MustMarshal(sgl *memsys.SGL) {
   205  	sgl.Write([]byte(xml.Header))
   206  	err := xml.NewEncoder(sgl).Encode(r)
   207  	debug.AssertNoErr(err)
   208  }
   209  
   210  func (r *InitiateMptUploadResult) MustMarshal(sgl *memsys.SGL) {
   211  	sgl.Write([]byte(xml.Header))
   212  	err := xml.NewEncoder(sgl).Encode(r)
   213  	debug.AssertNoErr(err)
   214  }
   215  
   216  func (r *CompleteMptUploadResult) MustMarshal(sgl *memsys.SGL) {
   217  	sgl.Write([]byte(xml.Header))
   218  	err := xml.NewEncoder(sgl).Encode(r)
   219  	debug.AssertNoErr(err)
   220  }
   221  
   222  func (r *ListPartsResult) MustMarshal(sgl *memsys.SGL) {
   223  	sgl.Write([]byte(xml.Header))
   224  	err := xml.NewEncoder(sgl).Encode(r)
   225  	debug.AssertNoErr(err)
   226  }
   227  
   228  func (r *ListMptUploadsResult) MustMarshal(sgl *memsys.SGL) {
   229  	sgl.Write([]byte(xml.Header))
   230  	err := xml.NewEncoder(sgl).Encode(r)
   231  	debug.AssertNoErr(err)
   232  }
   233  
   234  func (r *DeleteResult) MustMarshal(sgl *memsys.SGL) {
   235  	sgl.Write([]byte(xml.Header))
   236  	err := xml.NewEncoder(sgl).Encode(r)
   237  	debug.AssertNoErr(err)
   238  }