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.

497 lines
14 KiB

4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
7 months ago
7 months ago
4 years ago
7 months ago
3 years ago
3 years ago
3 years ago
4 years ago
more solid weed mount (#4089) * compare chunks by timestamp * fix slab clearing error * fix test compilation * move oldest chunk to sealed, instead of by fullness * lock on fh.entryViewCache * remove verbose logs * revert slat clearing * less logs * less logs * track write and read by timestamp * remove useless logic * add entry lock on file handle release * use mem chunk only, swap file chunk has problems * comment out code that maybe used later * add debug mode to compare data read and write * more efficient readResolvedChunks with linked list * small optimization * fix test compilation * minor fix on writer * add SeparateGarbageChunks * group chunks into sections * turn off debug mode * fix tests * fix tests * tmp enable swap file chunk * Revert "tmp enable swap file chunk" This reverts commit 985137ec472924e4815f258189f6ca9f2168a0a7. * simple refactoring * simple refactoring * do not re-use swap file chunk. Sealed chunks should not be re-used. * comment out debugging facilities * either mem chunk or swap file chunk is fine now * remove orderedMutex as *semaphore.Weighted not found impactful * optimize size calculation for changing large files * optimize performance to avoid going through the long list of chunks * still problems with swap file chunk * rename * tiny optimization * swap file chunk save only successfully read data * fix * enable both mem and swap file chunk * resolve chunks with range * rename * fix chunk interval list * also change file handle chunk group when adding chunks * pick in-active chunk with time-decayed counter * fix compilation * avoid nil with empty fh.entry * refactoring * rename * rename * refactor visible intervals to *list.List * refactor chunkViews to *list.List * add IntervalList for generic interval list * change visible interval to use IntervalList in generics * cahnge chunkViews to *IntervalList[*ChunkView] * use NewFileChunkSection to create * rename variables * refactor * fix renaming leftover * renaming * renaming * add insert interval * interval list adds lock * incrementally add chunks to readers Fixes: 1. set start and stop offset for the value object 2. clone the value object 3. use pointer instead of copy-by-value when passing to interval.Value 4. use insert interval since adding chunk could be out of order * fix tests compilation * fix tests compilation
2 years ago
more solid weed mount (#4089) * compare chunks by timestamp * fix slab clearing error * fix test compilation * move oldest chunk to sealed, instead of by fullness * lock on fh.entryViewCache * remove verbose logs * revert slat clearing * less logs * less logs * track write and read by timestamp * remove useless logic * add entry lock on file handle release * use mem chunk only, swap file chunk has problems * comment out code that maybe used later * add debug mode to compare data read and write * more efficient readResolvedChunks with linked list * small optimization * fix test compilation * minor fix on writer * add SeparateGarbageChunks * group chunks into sections * turn off debug mode * fix tests * fix tests * tmp enable swap file chunk * Revert "tmp enable swap file chunk" This reverts commit 985137ec472924e4815f258189f6ca9f2168a0a7. * simple refactoring * simple refactoring * do not re-use swap file chunk. Sealed chunks should not be re-used. * comment out debugging facilities * either mem chunk or swap file chunk is fine now * remove orderedMutex as *semaphore.Weighted not found impactful * optimize size calculation for changing large files * optimize performance to avoid going through the long list of chunks * still problems with swap file chunk * rename * tiny optimization * swap file chunk save only successfully read data * fix * enable both mem and swap file chunk * resolve chunks with range * rename * fix chunk interval list * also change file handle chunk group when adding chunks * pick in-active chunk with time-decayed counter * fix compilation * avoid nil with empty fh.entry * refactoring * rename * rename * refactor visible intervals to *list.List * refactor chunkViews to *list.List * add IntervalList for generic interval list * change visible interval to use IntervalList in generics * cahnge chunkViews to *IntervalList[*ChunkView] * use NewFileChunkSection to create * rename variables * refactor * fix renaming leftover * renaming * renaming * add insert interval * interval list adds lock * incrementally add chunks to readers Fixes: 1. set start and stop offset for the value object 2. clone the value object 3. use pointer instead of copy-by-value when passing to interval.Value 4. use insert interval since adding chunk could be out of order * fix tests compilation * fix tests compilation
2 years ago
4 years ago
  1. package weed_server
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "path"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "github.com/seaweedfs/seaweedfs/weed/filer"
  15. "github.com/seaweedfs/seaweedfs/weed/glog"
  16. "github.com/seaweedfs/seaweedfs/weed/operation"
  17. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  18. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  19. "github.com/seaweedfs/seaweedfs/weed/storage/needle"
  20. "github.com/seaweedfs/seaweedfs/weed/util"
  21. )
  22. func (fs *FilerServer) autoChunk(ctx context.Context, w http.ResponseWriter, r *http.Request, contentLength int64, so *operation.StorageOption) {
  23. // autoChunking can be set at the command-line level or as a query param. Query param overrides command-line
  24. query := r.URL.Query()
  25. parsedMaxMB, _ := strconv.ParseInt(query.Get("maxMB"), 10, 32)
  26. maxMB := int32(parsedMaxMB)
  27. if maxMB <= 0 && fs.option.MaxMB > 0 {
  28. maxMB = int32(fs.option.MaxMB)
  29. }
  30. chunkSize := 1024 * 1024 * maxMB
  31. var reply *FilerPostResult
  32. var err error
  33. var md5bytes []byte
  34. if r.Method == http.MethodPost {
  35. if r.Header.Get("Content-Type") == "" && strings.HasSuffix(r.URL.Path, "/") {
  36. reply, err = fs.mkdir(ctx, w, r, so)
  37. } else {
  38. reply, md5bytes, err = fs.doPostAutoChunk(ctx, w, r, chunkSize, contentLength, so)
  39. }
  40. } else {
  41. reply, md5bytes, err = fs.doPutAutoChunk(ctx, w, r, chunkSize, contentLength, so)
  42. }
  43. if err != nil {
  44. if err.Error() == "operation not permitted" {
  45. writeJsonError(w, r, http.StatusForbidden, err)
  46. } else if strings.HasPrefix(err.Error(), "read input:") || err.Error() == io.ErrUnexpectedEOF.Error() {
  47. writeJsonError(w, r, util.HttpStatusCancelled, err)
  48. } else if strings.HasSuffix(err.Error(), "is a file") || strings.HasSuffix(err.Error(), "already exists") {
  49. writeJsonError(w, r, http.StatusConflict, err)
  50. } else {
  51. writeJsonError(w, r, http.StatusInternalServerError, err)
  52. }
  53. } else if reply != nil {
  54. if len(md5bytes) > 0 {
  55. md5InBase64 := util.Base64Encode(md5bytes)
  56. w.Header().Set("Content-MD5", md5InBase64)
  57. }
  58. writeJsonQuiet(w, r, http.StatusCreated, reply)
  59. }
  60. }
  61. func (fs *FilerServer) doPostAutoChunk(ctx context.Context, w http.ResponseWriter, r *http.Request, chunkSize int32, contentLength int64, so *operation.StorageOption) (filerResult *FilerPostResult, md5bytes []byte, replyerr error) {
  62. multipartReader, multipartReaderErr := r.MultipartReader()
  63. if multipartReaderErr != nil {
  64. return nil, nil, multipartReaderErr
  65. }
  66. part1, part1Err := multipartReader.NextPart()
  67. if part1Err != nil {
  68. return nil, nil, part1Err
  69. }
  70. fileName := part1.FileName()
  71. if fileName != "" {
  72. fileName = path.Base(fileName)
  73. }
  74. contentType := part1.Header.Get("Content-Type")
  75. if contentType == "application/octet-stream" {
  76. contentType = ""
  77. }
  78. if err := fs.checkPermissions(ctx, r, fileName); err != nil {
  79. return nil, nil, err
  80. }
  81. if so.SaveInside {
  82. buf := bufPool.Get().(*bytes.Buffer)
  83. buf.Reset()
  84. buf.ReadFrom(part1)
  85. filerResult, replyerr = fs.saveMetaData(ctx, r, fileName, contentType, so, nil, nil, 0, buf.Bytes())
  86. bufPool.Put(buf)
  87. return
  88. }
  89. fileChunks, md5Hash, chunkOffset, err, smallContent := fs.uploadRequestToChunks(w, r, part1, chunkSize, fileName, contentType, contentLength, so)
  90. if err != nil {
  91. return nil, nil, err
  92. }
  93. md5bytes = md5Hash.Sum(nil)
  94. headerMd5 := r.Header.Get("Content-Md5")
  95. if headerMd5 != "" && !(util.Base64Encode(md5bytes) == headerMd5 || fmt.Sprintf("%x", md5bytes) == headerMd5) {
  96. fs.filer.DeleteUncommittedChunks(fileChunks)
  97. return nil, nil, errors.New("The Content-Md5 you specified did not match what we received.")
  98. }
  99. filerResult, replyerr = fs.saveMetaData(ctx, r, fileName, contentType, so, md5bytes, fileChunks, chunkOffset, smallContent)
  100. if replyerr != nil {
  101. fs.filer.DeleteUncommittedChunks(fileChunks)
  102. }
  103. return
  104. }
  105. func (fs *FilerServer) doPutAutoChunk(ctx context.Context, w http.ResponseWriter, r *http.Request, chunkSize int32, contentLength int64, so *operation.StorageOption) (filerResult *FilerPostResult, md5bytes []byte, replyerr error) {
  106. fileName := path.Base(r.URL.Path)
  107. contentType := r.Header.Get("Content-Type")
  108. if contentType == "application/octet-stream" {
  109. contentType = ""
  110. }
  111. if err := fs.checkPermissions(ctx, r, fileName); err != nil {
  112. return nil, nil, err
  113. }
  114. fileChunks, md5Hash, chunkOffset, err, smallContent := fs.uploadRequestToChunks(w, r, r.Body, chunkSize, fileName, contentType, contentLength, so)
  115. if err != nil {
  116. return nil, nil, err
  117. }
  118. md5bytes = md5Hash.Sum(nil)
  119. headerMd5 := r.Header.Get("Content-Md5")
  120. if headerMd5 != "" && !(util.Base64Encode(md5bytes) == headerMd5 || fmt.Sprintf("%x", md5bytes) == headerMd5) {
  121. fs.filer.DeleteUncommittedChunks(fileChunks)
  122. return nil, nil, errors.New("The Content-Md5 you specified did not match what we received.")
  123. }
  124. filerResult, replyerr = fs.saveMetaData(ctx, r, fileName, contentType, so, md5bytes, fileChunks, chunkOffset, smallContent)
  125. if replyerr != nil {
  126. fs.filer.DeleteUncommittedChunks(fileChunks)
  127. }
  128. return
  129. }
  130. func isAppend(r *http.Request) bool {
  131. return r.URL.Query().Get("op") == "append"
  132. }
  133. func skipCheckParentDirEntry(r *http.Request) bool {
  134. return r.URL.Query().Get("skipCheckParentDir") == "true"
  135. }
  136. func isS3Request(r *http.Request) bool {
  137. return r.Header.Get(s3_constants.AmzAuthType) != "" || r.Header.Get("X-Amz-Date") != ""
  138. }
  139. func (fs *FilerServer) checkPermissions(ctx context.Context, r *http.Request, fileName string) error {
  140. fullPath := fs.fixFilePath(ctx, r, fileName)
  141. enforced, err := fs.wormEnforcedForEntry(ctx, fullPath)
  142. if err != nil {
  143. return err
  144. } else if enforced {
  145. // you cannot change a worm file
  146. return errors.New("operation not permitted")
  147. }
  148. return nil
  149. }
  150. func (fs *FilerServer) wormEnforcedForEntry(ctx context.Context, fullPath string) (bool, error) {
  151. rule := fs.filer.FilerConf.MatchStorageRule(fullPath)
  152. if !rule.Worm {
  153. return false, nil
  154. }
  155. entry, err := fs.filer.FindEntry(ctx, util.FullPath(fullPath))
  156. if err != nil {
  157. if errors.Is(err, filer_pb.ErrNotFound) {
  158. return false, nil
  159. }
  160. return false, err
  161. }
  162. // worm is not enforced
  163. if entry.WORMEnforcedAtTsNs == 0 {
  164. return false, nil
  165. }
  166. // worm will never expire
  167. if rule.WormRetentionTimeSeconds == 0 {
  168. return true, nil
  169. }
  170. enforcedAt := time.Unix(0, entry.WORMEnforcedAtTsNs)
  171. // worm is expired
  172. if time.Now().Sub(enforcedAt).Seconds() >= float64(rule.WormRetentionTimeSeconds) {
  173. return false, nil
  174. }
  175. return true, nil
  176. }
  177. func (fs *FilerServer) fixFilePath(ctx context.Context, r *http.Request, fileName string) string {
  178. // fix the path
  179. fullPath := r.URL.Path
  180. if strings.HasSuffix(fullPath, "/") {
  181. if fileName != "" {
  182. fullPath += fileName
  183. }
  184. } else {
  185. if fileName != "" {
  186. if possibleDirEntry, findDirErr := fs.filer.FindEntry(ctx, util.FullPath(fullPath)); findDirErr == nil {
  187. if possibleDirEntry.IsDirectory() {
  188. fullPath += "/" + fileName
  189. }
  190. }
  191. }
  192. }
  193. return fullPath
  194. }
  195. func (fs *FilerServer) saveMetaData(ctx context.Context, r *http.Request, fileName string, contentType string, so *operation.StorageOption, md5bytes []byte, fileChunks []*filer_pb.FileChunk, chunkOffset int64, content []byte) (filerResult *FilerPostResult, replyerr error) {
  196. // detect file mode
  197. modeStr := r.URL.Query().Get("mode")
  198. if modeStr == "" {
  199. modeStr = "0660"
  200. }
  201. mode, err := strconv.ParseUint(modeStr, 8, 32)
  202. if err != nil {
  203. glog.Errorf("Invalid mode format: %s, use 0660 by default", modeStr)
  204. mode = 0660
  205. }
  206. // fix the path
  207. path := fs.fixFilePath(ctx, r, fileName)
  208. var entry *filer.Entry
  209. var newChunks []*filer_pb.FileChunk
  210. var mergedChunks []*filer_pb.FileChunk
  211. isAppend := isAppend(r)
  212. isOffsetWrite := len(fileChunks) > 0 && fileChunks[0].Offset > 0
  213. // when it is an append
  214. if isAppend || isOffsetWrite {
  215. existingEntry, findErr := fs.filer.FindEntry(ctx, util.FullPath(path))
  216. if findErr != nil && findErr != filer_pb.ErrNotFound {
  217. glog.V(0).Infof("failing to find %s: %v", path, findErr)
  218. }
  219. entry = existingEntry
  220. }
  221. if entry != nil {
  222. entry.Mtime = time.Now()
  223. entry.Md5 = nil
  224. // adjust chunk offsets
  225. if isAppend {
  226. for _, chunk := range fileChunks {
  227. chunk.Offset += int64(entry.FileSize)
  228. }
  229. entry.FileSize += uint64(chunkOffset)
  230. }
  231. newChunks = append(entry.GetChunks(), fileChunks...)
  232. // TODO
  233. if len(entry.Content) > 0 {
  234. replyerr = fmt.Errorf("append to small file is not supported yet")
  235. return
  236. }
  237. } else {
  238. glog.V(4).Infoln("saving", path)
  239. newChunks = fileChunks
  240. entry = &filer.Entry{
  241. FullPath: util.FullPath(path),
  242. Attr: filer.Attr{
  243. Mtime: time.Now(),
  244. Crtime: time.Now(),
  245. Mode: os.FileMode(mode),
  246. Uid: OS_UID,
  247. Gid: OS_GID,
  248. TtlSec: so.TtlSeconds,
  249. Mime: contentType,
  250. Md5: md5bytes,
  251. FileSize: uint64(chunkOffset),
  252. },
  253. Content: content,
  254. }
  255. }
  256. // maybe concatenate small chunks into one whole chunk
  257. mergedChunks, replyerr = fs.maybeMergeChunks(so, newChunks)
  258. if replyerr != nil {
  259. glog.V(0).Infof("merge chunks %s: %v", r.RequestURI, replyerr)
  260. mergedChunks = newChunks
  261. }
  262. // maybe compact entry chunks
  263. mergedChunks, replyerr = filer.MaybeManifestize(fs.saveAsChunk(so), mergedChunks)
  264. if replyerr != nil {
  265. glog.V(0).Infof("manifestize %s: %v", r.RequestURI, replyerr)
  266. return
  267. }
  268. entry.Chunks = mergedChunks
  269. if isOffsetWrite {
  270. entry.Md5 = nil
  271. entry.FileSize = entry.Size()
  272. }
  273. filerResult = &FilerPostResult{
  274. Name: fileName,
  275. Size: int64(entry.FileSize),
  276. }
  277. entry.Extended = SaveAmzMetaData(r, entry.Extended, false)
  278. for k, v := range r.Header {
  279. if len(v) > 0 && len(v[0]) > 0 {
  280. if strings.HasPrefix(k, needle.PairNamePrefix) || k == "Cache-Control" || k == "Expires" || k == "Content-Disposition" {
  281. entry.Extended[k] = []byte(v[0])
  282. }
  283. if k == "Response-Content-Disposition" {
  284. entry.Extended["Content-Disposition"] = []byte(v[0])
  285. }
  286. }
  287. }
  288. dbErr := fs.filer.CreateEntry(ctx, entry, false, false, nil, skipCheckParentDirEntry(r), so.MaxFileNameLength)
  289. // In test_bucket_listv2_delimiter_basic, the valid object key is the parent folder
  290. if dbErr != nil && strings.HasSuffix(dbErr.Error(), " is a file") && isS3Request(r) {
  291. dbErr = fs.filer.CreateEntry(ctx, entry, false, false, nil, true, so.MaxFileNameLength)
  292. }
  293. if dbErr != nil {
  294. replyerr = dbErr
  295. filerResult.Error = dbErr.Error()
  296. glog.V(0).Infof("failing to write %s to filer server : %v", path, dbErr)
  297. }
  298. return filerResult, replyerr
  299. }
  300. func (fs *FilerServer) saveAsChunk(so *operation.StorageOption) filer.SaveDataAsChunkFunctionType {
  301. return func(reader io.Reader, name string, offset int64, tsNs int64) (*filer_pb.FileChunk, error) {
  302. var fileId string
  303. var uploadResult *operation.UploadResult
  304. err := util.Retry("saveAsChunk", func() error {
  305. // assign one file id for one chunk
  306. assignedFileId, urlLocation, auth, assignErr := fs.assignNewFileInfo(so)
  307. if assignErr != nil {
  308. return assignErr
  309. }
  310. fileId = assignedFileId
  311. // upload the chunk to the volume server
  312. uploadOption := &operation.UploadOption{
  313. UploadUrl: urlLocation,
  314. Filename: name,
  315. Cipher: fs.option.Cipher,
  316. IsInputCompressed: false,
  317. MimeType: "",
  318. PairMap: nil,
  319. Jwt: auth,
  320. }
  321. uploader, uploaderErr := operation.NewUploader()
  322. if uploaderErr != nil {
  323. return uploaderErr
  324. }
  325. var uploadErr error
  326. uploadResult, uploadErr, _ = uploader.Upload(reader, uploadOption)
  327. if uploadErr != nil {
  328. return uploadErr
  329. }
  330. return nil
  331. })
  332. if err != nil {
  333. return nil, err
  334. }
  335. return uploadResult.ToPbFileChunk(fileId, offset, tsNs), nil
  336. }
  337. }
  338. func (fs *FilerServer) mkdir(ctx context.Context, w http.ResponseWriter, r *http.Request, so *operation.StorageOption) (filerResult *FilerPostResult, replyerr error) {
  339. // detect file mode
  340. modeStr := r.URL.Query().Get("mode")
  341. if modeStr == "" {
  342. modeStr = "0660"
  343. }
  344. mode, err := strconv.ParseUint(modeStr, 8, 32)
  345. if err != nil {
  346. glog.Errorf("Invalid mode format: %s, use 0660 by default", modeStr)
  347. mode = 0660
  348. }
  349. // fix the path
  350. path := r.URL.Path
  351. if strings.HasSuffix(path, "/") {
  352. path = path[:len(path)-1]
  353. }
  354. existingEntry, err := fs.filer.FindEntry(ctx, util.FullPath(path))
  355. if err == nil && existingEntry != nil {
  356. replyerr = fmt.Errorf("dir %s already exists", path)
  357. return
  358. }
  359. glog.V(4).Infoln("mkdir", path)
  360. entry := &filer.Entry{
  361. FullPath: util.FullPath(path),
  362. Attr: filer.Attr{
  363. Mtime: time.Now(),
  364. Crtime: time.Now(),
  365. Mode: os.FileMode(mode) | os.ModeDir,
  366. Uid: OS_UID,
  367. Gid: OS_GID,
  368. TtlSec: so.TtlSeconds,
  369. },
  370. }
  371. filerResult = &FilerPostResult{
  372. Name: util.FullPath(path).Name(),
  373. }
  374. if dbErr := fs.filer.CreateEntry(ctx, entry, false, false, nil, false, so.MaxFileNameLength); dbErr != nil {
  375. replyerr = dbErr
  376. filerResult.Error = dbErr.Error()
  377. glog.V(0).Infof("failing to create dir %s on filer server : %v", path, dbErr)
  378. }
  379. return filerResult, replyerr
  380. }
  381. func SaveAmzMetaData(r *http.Request, existing map[string][]byte, isReplace bool) (metadata map[string][]byte) {
  382. metadata = make(map[string][]byte)
  383. if !isReplace {
  384. for k, v := range existing {
  385. metadata[k] = v
  386. }
  387. }
  388. if sc := r.Header.Get(s3_constants.AmzStorageClass); sc != "" {
  389. metadata[s3_constants.AmzStorageClass] = []byte(sc)
  390. }
  391. if ce := r.Header.Get("Content-Encoding"); ce != "" {
  392. metadata["Content-Encoding"] = []byte(ce)
  393. }
  394. if tags := r.Header.Get(s3_constants.AmzObjectTagging); tags != "" {
  395. for _, v := range strings.Split(tags, "&") {
  396. tag := strings.Split(v, "=")
  397. if len(tag) == 2 {
  398. metadata[s3_constants.AmzObjectTagging+"-"+tag[0]] = []byte(tag[1])
  399. } else if len(tag) == 1 {
  400. metadata[s3_constants.AmzObjectTagging+"-"+tag[0]] = nil
  401. }
  402. }
  403. }
  404. for header, values := range r.Header {
  405. if strings.HasPrefix(header, s3_constants.AmzUserMetaPrefix) {
  406. for _, value := range values {
  407. metadata[header] = []byte(value)
  408. }
  409. }
  410. }
  411. //acp-owner
  412. acpOwner := r.Header.Get(s3_constants.ExtAmzOwnerKey)
  413. if len(acpOwner) > 0 {
  414. metadata[s3_constants.ExtAmzOwnerKey] = []byte(acpOwner)
  415. }
  416. //acp-grants
  417. acpGrants := r.Header.Get(s3_constants.ExtAmzAclKey)
  418. if len(acpOwner) > 0 {
  419. metadata[s3_constants.ExtAmzAclKey] = []byte(acpGrants)
  420. }
  421. return
  422. }