You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

396 lines
13 KiB

6 years ago
6 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
6 years ago
6 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
  1. package s3api
  2. import (
  3. "encoding/hex"
  4. "encoding/xml"
  5. "fmt"
  6. "math"
  7. "path/filepath"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "github.com/google/uuid"
  13. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  14. "golang.org/x/exp/slices"
  15. "github.com/aws/aws-sdk-go/aws"
  16. "github.com/aws/aws-sdk-go/service/s3"
  17. "github.com/seaweedfs/seaweedfs/weed/filer"
  18. "github.com/seaweedfs/seaweedfs/weed/glog"
  19. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  20. )
  21. type InitiateMultipartUploadResult struct {
  22. XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ InitiateMultipartUploadResult"`
  23. s3.CreateMultipartUploadOutput
  24. }
  25. func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInput) (output *InitiateMultipartUploadResult, code s3err.ErrorCode) {
  26. glog.V(2).Infof("createMultipartUpload input %v", input)
  27. uploadIdString := s3a.generateUploadID(*input.Key)
  28. uploadIdString = uploadIdString + "_" + strings.ReplaceAll(uuid.New().String(), "-", "")
  29. if err := s3a.mkdir(s3a.genUploadsFolder(*input.Bucket), uploadIdString, func(entry *filer_pb.Entry) {
  30. if entry.Extended == nil {
  31. entry.Extended = make(map[string][]byte)
  32. }
  33. entry.Extended["key"] = []byte(*input.Key)
  34. for k, v := range input.Metadata {
  35. entry.Extended[k] = []byte(*v)
  36. }
  37. if input.ContentType != nil {
  38. entry.Attributes.Mime = *input.ContentType
  39. }
  40. }); err != nil {
  41. glog.Errorf("NewMultipartUpload error: %v", err)
  42. return nil, s3err.ErrInternalError
  43. }
  44. output = &InitiateMultipartUploadResult{
  45. CreateMultipartUploadOutput: s3.CreateMultipartUploadOutput{
  46. Bucket: input.Bucket,
  47. Key: objectKey(input.Key),
  48. UploadId: aws.String(uploadIdString),
  49. },
  50. }
  51. return
  52. }
  53. type CompleteMultipartUploadResult struct {
  54. XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUploadResult"`
  55. s3.CompleteMultipartUploadOutput
  56. }
  57. func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploadInput, parts *CompleteMultipartUpload) (output *CompleteMultipartUploadResult, code s3err.ErrorCode) {
  58. glog.V(2).Infof("completeMultipartUpload input %v", input)
  59. completedParts := parts.Parts
  60. slices.SortFunc(completedParts, func(a, b CompletedPart) int {
  61. return a.PartNumber - b.PartNumber
  62. })
  63. uploadDirectory := s3a.genUploadsFolder(*input.Bucket) + "/" + *input.UploadId
  64. entries, _, err := s3a.list(uploadDirectory, "", "", false, maxPartsList)
  65. if err != nil || len(entries) == 0 {
  66. glog.Errorf("completeMultipartUpload %s %s error: %v, entries:%d", *input.Bucket, *input.UploadId, err, len(entries))
  67. return nil, s3err.ErrNoSuchUpload
  68. }
  69. pentry, err := s3a.getEntry(s3a.genUploadsFolder(*input.Bucket), *input.UploadId)
  70. if err != nil {
  71. glog.Errorf("completeMultipartUpload %s %s error: %v", *input.Bucket, *input.UploadId, err)
  72. return nil, s3err.ErrNoSuchUpload
  73. }
  74. partEntries := make(map[int][]*filer_pb.Entry, len(entries))
  75. for _, entry := range entries {
  76. glog.V(4).Infof("completeMultipartUpload part entries %s", entry.Name)
  77. if strings.HasSuffix(entry.Name, ".part") && !entry.IsDirectory {
  78. var partNumberString string
  79. index := strings.Index(entry.Name, "_")
  80. if index != -1 {
  81. partNumberString = entry.Name[:index]
  82. } else {
  83. partNumberString = entry.Name[:len(entry.Name)-len(".part")]
  84. }
  85. partNumber, err := strconv.Atoi(partNumberString)
  86. if err != nil {
  87. glog.Errorf("completeMultipartUpload failed to pasre partNumber %s:%s", partNumberString, err)
  88. continue
  89. }
  90. //there maybe multi same part, because of client retry
  91. partEntries[partNumber] = append(partEntries[partNumber], entry)
  92. }
  93. }
  94. mime := pentry.Attributes.Mime
  95. var finalParts []*filer_pb.FileChunk
  96. var offset int64
  97. var deleteEntries []*filer_pb.Entry
  98. for _, part := range completedParts {
  99. entries := partEntries[part.PartNumber]
  100. // check whether completedParts is more than received parts
  101. if len(entries) == 0 {
  102. glog.Errorf("part %d has no entry", part.PartNumber)
  103. return nil, s3err.ErrInvalidPart
  104. }
  105. found := false
  106. for _, entry := range entries {
  107. if found {
  108. deleteEntries = append(deleteEntries, entry)
  109. continue
  110. }
  111. partETag := strings.Trim(part.ETag, `"`)
  112. entryETag := hex.EncodeToString(entry.Attributes.GetMd5())
  113. glog.Warningf("complete etag %s, partEtag %s", partETag, entryETag)
  114. if partETag != "" && len(partETag) == 32 && entryETag != "" && entryETag != partETag {
  115. err = fmt.Errorf("completeMultipartUpload %s ETag mismatch chunk: %s part: %s", entry.Name, entryETag, partETag)
  116. deleteEntries = append(deleteEntries, entry)
  117. continue
  118. }
  119. for _, chunk := range entry.GetChunks() {
  120. p := &filer_pb.FileChunk{
  121. FileId: chunk.GetFileIdString(),
  122. Offset: offset,
  123. Size: chunk.Size,
  124. ModifiedTsNs: chunk.ModifiedTsNs,
  125. CipherKey: chunk.CipherKey,
  126. ETag: chunk.ETag,
  127. }
  128. finalParts = append(finalParts, p)
  129. offset += int64(chunk.Size)
  130. }
  131. found = true
  132. err = nil
  133. }
  134. if err != nil {
  135. glog.Errorf("%s", err)
  136. return nil, s3err.ErrInvalidPart
  137. }
  138. }
  139. entryName := filepath.Base(*input.Key)
  140. dirName := filepath.ToSlash(filepath.Dir(*input.Key))
  141. if dirName == "." {
  142. dirName = ""
  143. }
  144. if strings.HasPrefix(dirName, "/") {
  145. dirName = dirName[1:]
  146. }
  147. dirName = fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, *input.Bucket, dirName)
  148. // remove suffix '/'
  149. if strings.HasSuffix(dirName, "/") {
  150. dirName = dirName[:len(dirName)-1]
  151. }
  152. err = s3a.mkFile(dirName, entryName, finalParts, func(entry *filer_pb.Entry) {
  153. if entry.Extended == nil {
  154. entry.Extended = make(map[string][]byte)
  155. }
  156. for k, v := range pentry.Extended {
  157. if k != "key" {
  158. entry.Extended[k] = v
  159. }
  160. }
  161. if pentry.Attributes.Mime != "" {
  162. entry.Attributes.Mime = pentry.Attributes.Mime
  163. } else if mime != "" {
  164. entry.Attributes.Mime = mime
  165. }
  166. entry.Attributes.FileSize = uint64(offset)
  167. })
  168. if err != nil {
  169. glog.Errorf("completeMultipartUpload %s/%s error: %v", dirName, entryName, err)
  170. return nil, s3err.ErrInternalError
  171. }
  172. output = &CompleteMultipartUploadResult{
  173. CompleteMultipartUploadOutput: s3.CompleteMultipartUploadOutput{
  174. Location: aws.String(fmt.Sprintf("http://%s%s/%s", s3a.option.Filer.ToHttpAddress(), urlEscapeObject(dirName), urlPathEscape(entryName))),
  175. Bucket: input.Bucket,
  176. ETag: aws.String("\"" + filer.ETagChunks(finalParts) + "\""),
  177. Key: objectKey(input.Key),
  178. },
  179. }
  180. for _, deleteEntry := range deleteEntries {
  181. //delete unused part data
  182. glog.Infof("completeMultipartUpload cleanup %s upload %s unused %s", *input.Bucket, *input.UploadId, deleteEntry.Name)
  183. if err = s3a.rm(uploadDirectory, deleteEntry.Name, true, true); err != nil {
  184. glog.Warningf("completeMultipartUpload cleanup %s upload %s unused %s : %v", *input.Bucket, *input.UploadId, deleteEntry.Name, err)
  185. }
  186. }
  187. if err = s3a.rm(s3a.genUploadsFolder(*input.Bucket), *input.UploadId, false, true); err != nil {
  188. glog.V(1).Infof("completeMultipartUpload cleanup %s upload %s: %v", *input.Bucket, *input.UploadId, err)
  189. }
  190. return
  191. }
  192. func findByPartNumber(fileName string, parts []CompletedPart) (etag string, found bool) {
  193. partNumber, formatErr := strconv.Atoi(fileName[:4])
  194. if formatErr != nil {
  195. return
  196. }
  197. x := sort.Search(len(parts), func(i int) bool {
  198. return parts[i].PartNumber >= partNumber
  199. })
  200. if x >= len(parts) {
  201. return
  202. }
  203. if parts[x].PartNumber != partNumber {
  204. return
  205. }
  206. y := 0
  207. for i, part := range parts[x:] {
  208. if part.PartNumber == partNumber {
  209. y = i
  210. } else {
  211. break
  212. }
  213. }
  214. return parts[x+y].ETag, true
  215. }
  216. func (s3a *S3ApiServer) abortMultipartUpload(input *s3.AbortMultipartUploadInput) (output *s3.AbortMultipartUploadOutput, code s3err.ErrorCode) {
  217. glog.V(2).Infof("abortMultipartUpload input %v", input)
  218. exists, err := s3a.exists(s3a.genUploadsFolder(*input.Bucket), *input.UploadId, true)
  219. if err != nil {
  220. glog.V(1).Infof("bucket %s abort upload %s: %v", *input.Bucket, *input.UploadId, err)
  221. return nil, s3err.ErrNoSuchUpload
  222. }
  223. if exists {
  224. err = s3a.rm(s3a.genUploadsFolder(*input.Bucket), *input.UploadId, true, true)
  225. }
  226. if err != nil {
  227. glog.V(1).Infof("bucket %s remove upload %s: %v", *input.Bucket, *input.UploadId, err)
  228. return nil, s3err.ErrInternalError
  229. }
  230. return &s3.AbortMultipartUploadOutput{}, s3err.ErrNone
  231. }
  232. type ListMultipartUploadsResult struct {
  233. XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListMultipartUploadsResult"`
  234. // copied from s3.ListMultipartUploadsOutput, the Uploads is not converting to <Upload></Upload>
  235. Bucket *string `type:"string"`
  236. Delimiter *string `type:"string"`
  237. EncodingType *string `type:"string" enum:"EncodingType"`
  238. IsTruncated *bool `type:"boolean"`
  239. KeyMarker *string `type:"string"`
  240. MaxUploads *int64 `type:"integer"`
  241. NextKeyMarker *string `type:"string"`
  242. NextUploadIdMarker *string `type:"string"`
  243. Prefix *string `type:"string"`
  244. UploadIdMarker *string `type:"string"`
  245. Upload []*s3.MultipartUpload `locationName:"Upload" type:"list" flattened:"true"`
  246. }
  247. func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput) (output *ListMultipartUploadsResult, code s3err.ErrorCode) {
  248. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html
  249. glog.V(2).Infof("listMultipartUploads input %v", input)
  250. output = &ListMultipartUploadsResult{
  251. Bucket: input.Bucket,
  252. Delimiter: input.Delimiter,
  253. EncodingType: input.EncodingType,
  254. KeyMarker: input.KeyMarker,
  255. MaxUploads: input.MaxUploads,
  256. Prefix: input.Prefix,
  257. IsTruncated: aws.Bool(false),
  258. }
  259. entries, _, err := s3a.list(s3a.genUploadsFolder(*input.Bucket), "", *input.UploadIdMarker, false, math.MaxInt32)
  260. if err != nil {
  261. glog.Errorf("listMultipartUploads %s error: %v", *input.Bucket, err)
  262. return
  263. }
  264. uploadsCount := int64(0)
  265. for _, entry := range entries {
  266. if entry.Extended != nil {
  267. key := string(entry.Extended["key"])
  268. if *input.KeyMarker != "" && *input.KeyMarker != key {
  269. continue
  270. }
  271. if *input.Prefix != "" && !strings.HasPrefix(key, *input.Prefix) {
  272. continue
  273. }
  274. output.Upload = append(output.Upload, &s3.MultipartUpload{
  275. Key: objectKey(aws.String(key)),
  276. UploadId: aws.String(entry.Name),
  277. })
  278. uploadsCount += 1
  279. }
  280. if uploadsCount >= *input.MaxUploads {
  281. output.IsTruncated = aws.Bool(true)
  282. output.NextUploadIdMarker = aws.String(entry.Name)
  283. break
  284. }
  285. }
  286. return
  287. }
  288. type ListPartsResult struct {
  289. XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListPartsResult"`
  290. // copied from s3.ListPartsOutput, the Parts is not converting to <Part></Part>
  291. Bucket *string `type:"string"`
  292. IsTruncated *bool `type:"boolean"`
  293. Key *string `min:"1" type:"string"`
  294. MaxParts *int64 `type:"integer"`
  295. NextPartNumberMarker *int64 `type:"integer"`
  296. PartNumberMarker *int64 `type:"integer"`
  297. Part []*s3.Part `locationName:"Part" type:"list" flattened:"true"`
  298. StorageClass *string `type:"string" enum:"StorageClass"`
  299. UploadId *string `type:"string"`
  300. }
  301. func (s3a *S3ApiServer) listObjectParts(input *s3.ListPartsInput) (output *ListPartsResult, code s3err.ErrorCode) {
  302. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html
  303. glog.V(2).Infof("listObjectParts input %v", input)
  304. output = &ListPartsResult{
  305. Bucket: input.Bucket,
  306. Key: objectKey(input.Key),
  307. UploadId: input.UploadId,
  308. MaxParts: input.MaxParts, // the maximum number of parts to return.
  309. PartNumberMarker: input.PartNumberMarker, // the part number starts after this, exclusive
  310. StorageClass: aws.String("STANDARD"),
  311. }
  312. entries, isLast, err := s3a.list(s3a.genUploadsFolder(*input.Bucket)+"/"+*input.UploadId, "", fmt.Sprintf("%04d.part", *input.PartNumberMarker), false, uint32(*input.MaxParts))
  313. if err != nil {
  314. glog.Errorf("listObjectParts %s %s error: %v", *input.Bucket, *input.UploadId, err)
  315. return nil, s3err.ErrNoSuchUpload
  316. }
  317. // Note: The upload directory is sort of a marker of the existence of an multipart upload request.
  318. // So can not just delete empty upload folders.
  319. output.IsTruncated = aws.Bool(!isLast)
  320. for _, entry := range entries {
  321. if strings.HasSuffix(entry.Name, ".part") && !entry.IsDirectory {
  322. partNumberString := entry.Name[:len(entry.Name)-len(".part")]
  323. partNumber, err := strconv.Atoi(partNumberString)
  324. if err != nil {
  325. glog.Errorf("listObjectParts %s %s parse %s: %v", *input.Bucket, *input.UploadId, entry.Name, err)
  326. continue
  327. }
  328. output.Part = append(output.Part, &s3.Part{
  329. PartNumber: aws.Int64(int64(partNumber)),
  330. LastModified: aws.Time(time.Unix(entry.Attributes.Mtime, 0).UTC()),
  331. Size: aws.Int64(int64(filer.FileSize(entry))),
  332. ETag: aws.String("\"" + filer.ETag(entry) + "\""),
  333. })
  334. if !isLast {
  335. output.NextPartNumberMarker = aws.Int64(int64(partNumber))
  336. }
  337. }
  338. }
  339. return
  340. }