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 }