2016-06-03 03:05:34 +00:00
package weed_server
import (
2019-03-15 22:26:09 +00:00
"context"
2016-06-03 03:05:34 +00:00
"encoding/json"
"errors"
2019-06-23 05:53:52 +00:00
"fmt"
2019-04-15 06:28:24 +00:00
"io"
2016-06-03 03:05:34 +00:00
"io/ioutil"
2019-03-27 21:25:18 +00:00
"mime"
2016-06-03 03:05:34 +00:00
"net/http"
"net/url"
2019-02-15 08:09:19 +00:00
"os"
2019-03-27 21:25:18 +00:00
filenamePath "path"
2017-01-08 01:16:29 +00:00
"strconv"
2017-01-08 14:34:26 +00:00
"strings"
2018-05-28 06:59:49 +00:00
"time"
2017-01-08 01:16:29 +00:00
2018-05-27 18:52:26 +00:00
"github.com/chrislusf/seaweedfs/weed/filer2"
2016-06-03 03:05:34 +00:00
"github.com/chrislusf/seaweedfs/weed/glog"
"github.com/chrislusf/seaweedfs/weed/operation"
2018-05-27 18:52:26 +00:00
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
2019-02-15 08:09:19 +00:00
"github.com/chrislusf/seaweedfs/weed/security"
2019-06-15 19:21:44 +00:00
"github.com/chrislusf/seaweedfs/weed/stats"
2016-06-03 03:05:34 +00:00
"github.com/chrislusf/seaweedfs/weed/util"
2018-07-22 00:47:59 +00:00
)
var (
OS_UID = uint32 ( os . Getuid ( ) )
OS_GID = uint32 ( os . Getgid ( ) )
2016-06-03 03:05:34 +00:00
)
2016-06-03 03:44:50 +00:00
type FilerPostResult struct {
Name string ` json:"name,omitempty" `
2020-02-04 01:04:06 +00:00
Size int64 ` json:"size,omitempty" `
2016-06-03 03:44:50 +00:00
Error string ` json:"error,omitempty" `
Fid string ` json:"fid,omitempty" `
Url string ` json:"url,omitempty" `
}
2019-02-15 08:09:19 +00:00
func ( fs * FilerServer ) assignNewFileInfo ( w http . ResponseWriter , r * http . Request , replication , collection string , dataCenter string ) ( fileId , urlLocation string , auth security . EncodedJwt , err error ) {
2019-06-23 05:53:52 +00:00
stats . FilerRequestCounter . WithLabelValues ( "assign" ) . Inc ( )
start := time . Now ( )
defer func ( ) { stats . FilerRequestHistogram . WithLabelValues ( "assign" ) . Observe ( time . Since ( start ) . Seconds ( ) ) } ( )
2016-06-26 02:50:18 +00:00
ar := & operation . VolumeAssignRequest {
Count : 1 ,
Replication : replication ,
Collection : collection ,
Ttl : r . URL . Query ( ) . Get ( "ttl" ) ,
2018-07-14 20:36:28 +00:00
DataCenter : dataCenter ,
2016-06-26 02:50:18 +00:00
}
2018-07-10 07:20:50 +00:00
var altRequest * operation . VolumeAssignRequest
2018-07-14 20:36:28 +00:00
if dataCenter != "" {
2018-07-10 07:20:50 +00:00
altRequest = & operation . VolumeAssignRequest {
Count : 1 ,
Replication : replication ,
Collection : collection ,
Ttl : r . URL . Query ( ) . Get ( "ttl" ) ,
DataCenter : "" ,
}
}
2018-07-21 17:39:02 +00:00
2019-02-18 20:11:52 +00:00
assignResult , ae := operation . Assign ( fs . filer . GetMaster ( ) , fs . grpcDialOption , ar , altRequest )
2016-06-08 07:46:14 +00:00
if ae != nil {
2018-07-21 17:39:02 +00:00
glog . Errorf ( "failing to assign a file id: %v" , ae )
2016-06-08 07:46:14 +00:00
writeJsonError ( w , r , http . StatusInternalServerError , ae )
err = ae
return
}
fileId = assignResult . Fid
urlLocation = "http://" + assignResult . Url + "/" + assignResult . Fid
2019-02-15 08:09:19 +00:00
auth = assignResult . Auth
2016-06-08 07:46:14 +00:00
return
}
2016-06-03 03:05:34 +00:00
func ( fs * FilerServer ) PostHandler ( w http . ResponseWriter , r * http . Request ) {
2016-08-05 22:01:30 +00:00
2019-03-15 22:55:34 +00:00
ctx := context . Background ( )
2016-06-03 03:05:34 +00:00
query := r . URL . Query ( )
2020-02-25 06:28:45 +00:00
collection , replication := fs . detectCollection ( r . RequestURI , query . Get ( "collection" ) , query . Get ( "replication" ) )
2018-07-14 20:36:28 +00:00
dataCenter := query . Get ( "dataCenter" )
if dataCenter == "" {
dataCenter = fs . option . DataCenter
}
2016-06-03 03:05:34 +00:00
2019-03-15 22:55:34 +00:00
if autoChunked := fs . autoChunk ( ctx , w , r , replication , collection , dataCenter ) ; autoChunked {
2016-08-05 22:01:30 +00:00
return
}
2020-03-07 14:06:58 +00:00
if fs . option . Cipher {
reply , err := fs . encrypt ( ctx , w , r , replication , collection , dataCenter )
if err != nil {
writeJsonError ( w , r , http . StatusInternalServerError , err )
} else if reply != nil {
writeJsonQuiet ( w , r , http . StatusCreated , reply )
}
return
}
2019-02-15 08:09:19 +00:00
fileId , urlLocation , auth , err := fs . assignNewFileInfo ( w , r , replication , collection , dataCenter )
2019-01-02 20:58:26 +00:00
2018-07-21 17:39:02 +00:00
if err != nil || fileId == "" || urlLocation == "" {
2019-01-02 20:58:26 +00:00
glog . V ( 0 ) . Infof ( "fail to allocate volume for %s, collection:%s, datacenter:%s" , r . URL . Path , collection , dataCenter )
2020-03-07 14:06:58 +00:00
writeJsonError ( w , r , http . StatusInternalServerError , fmt . Errorf ( "fail to allocate volume for %s, collection:%s, datacenter:%s" , r . URL . Path , collection , dataCenter ) )
2018-07-21 17:39:02 +00:00
return
}
2018-09-12 07:46:12 +00:00
glog . V ( 4 ) . Infof ( "write %s to %v" , r . URL . Path , urlLocation )
2016-06-03 03:05:34 +00:00
u , _ := url . Parse ( urlLocation )
2016-08-05 22:01:30 +00:00
// This allows a client to generate a chunk manifest and submit it to the filer -- it is a little off
// because they need to provide FIDs instead of file paths...
cm , _ := strconv . ParseBool ( query . Get ( "cm" ) )
if cm {
q := u . Query ( )
q . Set ( "cm" , "true" )
u . RawQuery = q . Encode ( )
}
2016-06-03 03:05:34 +00:00
glog . V ( 4 ) . Infoln ( "post to" , u )
2016-08-05 22:01:30 +00:00
2019-06-23 05:53:52 +00:00
ret , err := fs . uploadToVolumeServer ( r , u , auth , w , fileId )
if err != nil {
2016-06-03 03:05:34 +00:00
return
}
2019-06-23 05:53:52 +00:00
if err = fs . updateFilerStore ( ctx , r , w , replication , collection , ret , fileId ) ; err != nil {
2016-06-03 03:05:34 +00:00
return
}
2018-07-22 00:54:14 +00:00
2019-06-23 05:53:52 +00:00
// send back post result
reply := FilerPostResult {
Name : ret . Name ,
2020-02-04 01:04:06 +00:00
Size : int64 ( ret . Size ) ,
2019-06-23 05:53:52 +00:00
Error : ret . Error ,
Fid : fileId ,
Url : urlLocation ,
2016-06-03 03:05:34 +00:00
}
2019-06-23 05:53:52 +00:00
setEtag ( w , ret . ETag )
writeJsonQuiet ( w , r , http . StatusCreated , reply )
}
// update metadata in filer store
func ( fs * FilerServer ) updateFilerStore ( ctx context . Context , r * http . Request , w http . ResponseWriter ,
2020-03-07 14:06:58 +00:00
replication string , collection string , ret * operation . UploadResult , fileId string ) ( err error ) {
2019-06-23 05:53:52 +00:00
stats . FilerRequestCounter . WithLabelValues ( "postStoreWrite" ) . Inc ( )
start := time . Now ( )
2019-06-23 08:57:51 +00:00
defer func ( ) {
stats . FilerRequestHistogram . WithLabelValues ( "postStoreWrite" ) . Observe ( time . Since ( start ) . Seconds ( ) )
} ( )
2016-06-03 07:24:55 +00:00
2019-11-26 16:04:47 +00:00
modeStr := r . URL . Query ( ) . Get ( "mode" )
if modeStr == "" {
modeStr = "0660"
}
mode , err := strconv . ParseUint ( modeStr , 8 , 32 )
if err != nil {
glog . Errorf ( "Invalid mode format: %s, use 0660 by default" , modeStr )
mode = 0660
}
2019-06-23 05:53:52 +00:00
path := r . URL . Path
2019-07-11 11:20:03 +00:00
if strings . HasSuffix ( path , "/" ) {
if ret . Name != "" {
path += ret . Name
}
}
2019-03-15 22:55:34 +00:00
existingEntry , err := fs . filer . FindEntry ( ctx , filer2 . FullPath ( path ) )
2018-12-17 08:20:00 +00:00
crTime := time . Now ( )
if err == nil && existingEntry != nil {
2019-07-11 11:20:03 +00:00
crTime = existingEntry . Crtime
2018-12-17 08:20:00 +00:00
}
2018-05-14 06:56:16 +00:00
entry := & filer2 . Entry {
FullPath : filer2 . FullPath ( path ) ,
Attr : filer2 . Attr {
2018-06-10 23:57:32 +00:00
Mtime : time . Now ( ) ,
2018-12-17 08:20:00 +00:00
Crtime : crTime ,
2019-11-26 16:04:47 +00:00
Mode : os . FileMode ( mode ) ,
2018-07-22 00:47:59 +00:00
Uid : OS_UID ,
Gid : OS_GID ,
2018-06-10 23:57:32 +00:00
Replication : replication ,
Collection : collection ,
TtlSec : int32 ( util . ParseInt ( r . URL . Query ( ) . Get ( "ttl" ) , 0 ) ) ,
2020-03-07 15:25:15 +00:00
Mime : ret . Mime ,
2018-05-14 06:56:16 +00:00
} ,
2018-05-16 07:08:44 +00:00
Chunks : [ ] * filer_pb . FileChunk { {
FileId : fileId ,
2018-07-22 00:47:59 +00:00
Size : uint64 ( ret . Size ) ,
2018-05-16 07:54:44 +00:00
Mtime : time . Now ( ) . UnixNano ( ) ,
2019-06-23 05:53:52 +00:00
ETag : ret . ETag ,
2018-05-14 06:56:16 +00:00
} } ,
}
2020-03-07 15:25:15 +00:00
if entry . Attr . Mime == "" {
if ext := filenamePath . Ext ( path ) ; ext != "" {
entry . Attr . Mime = mime . TypeByExtension ( ext )
}
2019-03-27 21:25:18 +00:00
}
2019-01-02 20:58:26 +00:00
// glog.V(4).Infof("saving %s => %+v", path, entry)
2020-01-22 19:42:40 +00:00
if dbErr := fs . filer . CreateEntry ( ctx , entry , false ) ; dbErr != nil {
2019-12-13 08:23:05 +00:00
fs . filer . DeleteChunks ( entry . Chunks )
2019-06-23 05:53:52 +00:00
glog . V ( 0 ) . Infof ( "failing to write %s to filer server : %v" , path , dbErr )
writeJsonError ( w , r , http . StatusInternalServerError , dbErr )
err = dbErr
2016-06-03 03:05:34 +00:00
return
}
2016-06-03 03:44:50 +00:00
2019-06-23 05:53:52 +00:00
return nil
}
// send request to volume server
2020-03-07 14:06:58 +00:00
func ( fs * FilerServer ) uploadToVolumeServer ( r * http . Request , u * url . URL , auth security . EncodedJwt , w http . ResponseWriter , fileId string ) ( ret * operation . UploadResult , err error ) {
2019-06-23 05:53:52 +00:00
stats . FilerRequestCounter . WithLabelValues ( "postUpload" ) . Inc ( )
start := time . Now ( )
defer func ( ) { stats . FilerRequestHistogram . WithLabelValues ( "postUpload" ) . Observe ( time . Since ( start ) . Seconds ( ) ) } ( )
2020-03-07 14:06:58 +00:00
ret = & operation . UploadResult { }
2019-06-23 05:53:52 +00:00
request := & http . Request {
Method : r . Method ,
URL : u ,
Proto : r . Proto ,
ProtoMajor : r . ProtoMajor ,
ProtoMinor : r . ProtoMinor ,
Header : r . Header ,
Body : r . Body ,
Host : r . Host ,
ContentLength : r . ContentLength ,
2016-06-03 03:44:50 +00:00
}
2020-03-07 14:06:58 +00:00
2019-06-23 05:53:52 +00:00
if auth != "" {
request . Header . Set ( "Authorization" , "BEARER " + string ( auth ) )
}
resp , doErr := util . Do ( request )
if doErr != nil {
glog . Errorf ( "failing to connect to volume server %s: %v, %+v" , r . RequestURI , doErr , r . Method )
writeJsonError ( w , r , http . StatusInternalServerError , doErr )
err = doErr
return
}
defer func ( ) {
io . Copy ( ioutil . Discard , resp . Body )
resp . Body . Close ( )
} ( )
etag := resp . Header . Get ( "ETag" )
respBody , raErr := ioutil . ReadAll ( resp . Body )
if raErr != nil {
glog . V ( 0 ) . Infoln ( "failing to upload to volume server" , r . RequestURI , raErr . Error ( ) )
writeJsonError ( w , r , http . StatusInternalServerError , raErr )
err = raErr
return
}
glog . V ( 4 ) . Infoln ( "post result" , string ( respBody ) )
unmarshalErr := json . Unmarshal ( respBody , & ret )
if unmarshalErr != nil {
glog . V ( 0 ) . Infoln ( "failing to read upload resonse" , r . RequestURI , string ( respBody ) )
writeJsonError ( w , r , http . StatusInternalServerError , unmarshalErr )
err = unmarshalErr
return
}
if ret . Error != "" {
err = errors . New ( ret . Error )
glog . V ( 0 ) . Infoln ( "failing to post to volume server" , r . RequestURI , ret . Error )
writeJsonError ( w , r , http . StatusInternalServerError , err )
return
}
// find correct final path
path := r . URL . Path
if strings . HasSuffix ( path , "/" ) {
if ret . Name != "" {
path += ret . Name
} else {
err = fmt . Errorf ( "can not to write to folder %s without a file name" , path )
fs . filer . DeleteFileByFileId ( fileId )
glog . V ( 0 ) . Infoln ( "Can not to write to folder" , path , "without a file name!" )
writeJsonError ( w , r , http . StatusInternalServerError , err )
return
}
}
if etag != "" {
ret . ETag = etag
}
return
2016-06-03 03:05:34 +00:00
}
// curl -X DELETE http://localhost:8888/path/to
2018-07-25 05:33:26 +00:00
// curl -X DELETE http://localhost:8888/path/to?recursive=true
2019-09-12 03:26:20 +00:00
// curl -X DELETE http://localhost:8888/path/to?recursive=true&ignoreRecursiveError=true
2019-12-11 22:58:22 +00:00
// curl -X DELETE http://localhost:8888/path/to?recursive=true&skipChunkDeletion=true
2016-06-03 03:05:34 +00:00
func ( fs * FilerServer ) DeleteHandler ( w http . ResponseWriter , r * http . Request ) {
2018-05-14 06:56:16 +00:00
2018-07-25 05:33:26 +00:00
isRecursive := r . FormValue ( "recursive" ) == "true"
2019-12-31 19:52:54 +00:00
if ! isRecursive && fs . option . recursiveDelete {
if r . FormValue ( "recursive" ) != "false" {
isRecursive = true
}
}
2019-09-12 03:26:20 +00:00
ignoreRecursiveError := r . FormValue ( "ignoreRecursiveError" ) == "true"
2019-12-11 22:58:22 +00:00
skipChunkDeletion := r . FormValue ( "skipChunkDeletion" ) == "true"
2018-07-25 05:33:26 +00:00
2019-12-11 22:58:22 +00:00
err := fs . filer . DeleteEntryMetaAndData ( context . Background ( ) , filer2 . FullPath ( r . URL . Path ) , isRecursive , ignoreRecursiveError , ! skipChunkDeletion )
2018-05-14 06:56:16 +00:00
if err != nil {
2018-07-22 01:47:23 +00:00
glog . V ( 1 ) . Infoln ( "deleting" , r . URL . Path , ":" , err . Error ( ) )
2019-12-19 05:04:40 +00:00
httpStatus := http . StatusInternalServerError
if err == filer2 . ErrNotFound {
httpStatus = http . StatusNotFound
}
writeJsonError ( w , r , httpStatus , err )
2018-05-14 06:56:16 +00:00
return
}
2018-07-22 01:49:47 +00:00
w . WriteHeader ( http . StatusNoContent )
2016-06-03 03:05:34 +00:00
}
2020-02-25 06:28:45 +00:00
func ( fs * FilerServer ) detectCollection ( requestURI , qCollection , qReplication string ) ( collection , replication string ) {
// default
collection = fs . option . Collection
replication = fs . option . DefaultReplication
// get default collection settings
if qCollection != "" {
collection = qCollection
}
if qReplication != "" {
replication = qReplication
}
// required by buckets folder
if strings . HasPrefix ( requestURI , fs . filer . DirBucketsPath + "/" ) {
bucketAndObjectKey := requestURI [ len ( fs . filer . DirBucketsPath ) + 1 : ]
t := strings . Index ( bucketAndObjectKey , "/" )
if t < 0 {
collection = bucketAndObjectKey
}
if t > 0 {
collection = bucketAndObjectKey [ : t ]
}
replication = fs . filer . ReadBucketOption ( collection )
}
return
}