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.

525 lines
16 KiB

5 years ago
4 years ago
7 years ago
4 years ago
5 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
  1. package command
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. "google.golang.org/grpc"
  16. "github.com/chrislusf/seaweedfs/weed/util/grace"
  17. "github.com/chrislusf/seaweedfs/weed/operation"
  18. "github.com/chrislusf/seaweedfs/weed/pb"
  19. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  20. "github.com/chrislusf/seaweedfs/weed/security"
  21. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  22. "github.com/chrislusf/seaweedfs/weed/util"
  23. "github.com/chrislusf/seaweedfs/weed/wdclient"
  24. )
  25. var (
  26. copy CopyOptions
  27. waitGroup sync.WaitGroup
  28. )
  29. type CopyOptions struct {
  30. include *string
  31. replication *string
  32. collection *string
  33. ttl *string
  34. diskType *string
  35. maxMB *int
  36. masterClient *wdclient.MasterClient
  37. concurrenctFiles *int
  38. concurrenctChunks *int
  39. grpcDialOption grpc.DialOption
  40. masters []string
  41. cipher bool
  42. ttlSec int32
  43. }
  44. func init() {
  45. cmdCopy.Run = runCopy // break init cycle
  46. cmdCopy.IsDebug = cmdCopy.Flag.Bool("debug", false, "verbose debug information")
  47. copy.include = cmdCopy.Flag.String("include", "", "pattens of files to copy, e.g., *.pdf, *.html, ab?d.txt, works together with -dir")
  48. copy.replication = cmdCopy.Flag.String("replication", "", "replication type")
  49. copy.collection = cmdCopy.Flag.String("collection", "", "optional collection name")
  50. copy.ttl = cmdCopy.Flag.String("ttl", "", "time to live, e.g.: 1m, 1h, 1d, 1M, 1y")
  51. copy.diskType = cmdCopy.Flag.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
  52. copy.maxMB = cmdCopy.Flag.Int("maxMB", 4, "split files larger than the limit")
  53. copy.concurrenctFiles = cmdCopy.Flag.Int("c", 8, "concurrent file copy goroutines")
  54. copy.concurrenctChunks = cmdCopy.Flag.Int("concurrentChunks", 8, "concurrent chunk copy goroutines for each file")
  55. }
  56. var cmdCopy = &Command{
  57. UsageLine: "filer.copy file_or_dir1 [file_or_dir2 file_or_dir3] http://localhost:8888/path/to/a/folder/",
  58. Short: "copy one or a list of files to a filer folder",
  59. Long: `copy one or a list of files, or batch copy one whole folder recursively, to a filer folder
  60. It can copy one or a list of files or folders.
  61. If copying a whole folder recursively:
  62. All files under the folder and subfolders will be copyed.
  63. Optional parameter "-include" allows you to specify the file name patterns.
  64. If "maxMB" is set to a positive number, files larger than it would be split into chunks.
  65. `,
  66. }
  67. func runCopy(cmd *Command, args []string) bool {
  68. util.LoadConfiguration("security", false)
  69. if len(args) <= 1 {
  70. return false
  71. }
  72. filerDestination := args[len(args)-1]
  73. fileOrDirs := args[0 : len(args)-1]
  74. filerUrl, err := url.Parse(filerDestination)
  75. if err != nil {
  76. fmt.Printf("The last argument should be a URL on filer: %v\n", err)
  77. return false
  78. }
  79. urlPath := filerUrl.Path
  80. if !strings.HasSuffix(urlPath, "/") {
  81. fmt.Printf("The last argument should be a folder and end with \"/\"\n")
  82. return false
  83. }
  84. if filerUrl.Port() == "" {
  85. fmt.Printf("The filer port should be specified.\n")
  86. return false
  87. }
  88. filerPort, parseErr := strconv.ParseUint(filerUrl.Port(), 10, 64)
  89. if parseErr != nil {
  90. fmt.Printf("The filer port parse error: %v\n", parseErr)
  91. return false
  92. }
  93. filerGrpcPort := filerPort + 10000
  94. filerGrpcAddress := fmt.Sprintf("%s:%d", filerUrl.Hostname(), filerGrpcPort)
  95. copy.grpcDialOption = security.LoadClientTLS(util.GetViper(), "grpc.client")
  96. masters, collection, replication, dirBuckets, maxMB, cipher, err := readFilerConfiguration(copy.grpcDialOption, filerGrpcAddress)
  97. if err != nil {
  98. fmt.Printf("read from filer %s: %v\n", filerGrpcAddress, err)
  99. return false
  100. }
  101. if strings.HasPrefix(urlPath, dirBuckets+"/") {
  102. restPath := urlPath[len(dirBuckets)+1:]
  103. if strings.Index(restPath, "/") > 0 {
  104. expectedBucket := restPath[:strings.Index(restPath, "/")]
  105. if *copy.collection == "" {
  106. *copy.collection = expectedBucket
  107. } else if *copy.collection != expectedBucket {
  108. fmt.Printf("destination %s uses collection \"%s\": unexpected collection \"%v\"\n", urlPath, expectedBucket, *copy.collection)
  109. return true
  110. }
  111. }
  112. }
  113. if *copy.collection == "" {
  114. *copy.collection = collection
  115. }
  116. if *copy.replication == "" {
  117. *copy.replication = replication
  118. }
  119. if *copy.maxMB == 0 {
  120. *copy.maxMB = int(maxMB)
  121. }
  122. copy.masters = masters
  123. copy.cipher = cipher
  124. ttl, err := needle.ReadTTL(*copy.ttl)
  125. if err != nil {
  126. fmt.Printf("parsing ttl %s: %v\n", *copy.ttl, err)
  127. return false
  128. }
  129. copy.ttlSec = int32(ttl.Minutes()) * 60
  130. if *cmdCopy.IsDebug {
  131. grace.SetupProfiling("filer.copy.cpu.pprof", "filer.copy.mem.pprof")
  132. }
  133. fileCopyTaskChan := make(chan FileCopyTask, *copy.concurrenctFiles)
  134. go func() {
  135. defer close(fileCopyTaskChan)
  136. for _, fileOrDir := range fileOrDirs {
  137. if err := genFileCopyTask(fileOrDir, urlPath, fileCopyTaskChan); err != nil {
  138. fmt.Fprintf(os.Stderr, "genFileCopyTask : %v\n", err)
  139. break
  140. }
  141. }
  142. }()
  143. for i := 0; i < *copy.concurrenctFiles; i++ {
  144. waitGroup.Add(1)
  145. go func() {
  146. defer waitGroup.Done()
  147. worker := FileCopyWorker{
  148. options: &copy,
  149. filerHost: filerUrl.Host,
  150. filerGrpcAddress: filerGrpcAddress,
  151. }
  152. if err := worker.copyFiles(fileCopyTaskChan); err != nil {
  153. fmt.Fprintf(os.Stderr, "copy file error: %v\n", err)
  154. return
  155. }
  156. }()
  157. }
  158. waitGroup.Wait()
  159. return true
  160. }
  161. func readFilerConfiguration(grpcDialOption grpc.DialOption, filerGrpcAddress string) (masters []string, collection, replication string, dirBuckets string, maxMB uint32, cipher bool, err error) {
  162. err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  163. resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
  164. if err != nil {
  165. return fmt.Errorf("get filer %s configuration: %v", filerGrpcAddress, err)
  166. }
  167. masters, collection, replication, maxMB = resp.Masters, resp.Collection, resp.Replication, resp.MaxMb
  168. dirBuckets = resp.DirBuckets
  169. cipher = resp.Cipher
  170. return nil
  171. })
  172. return
  173. }
  174. func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan FileCopyTask) error {
  175. fi, err := os.Stat(fileOrDir)
  176. if err != nil {
  177. fmt.Fprintf(os.Stderr, "Error: read file %s: %v\n", fileOrDir, err)
  178. return nil
  179. }
  180. mode := fi.Mode()
  181. uid, gid := util.GetFileUidGid(fi)
  182. fileCopyTaskChan <- FileCopyTask{
  183. sourceLocation: fileOrDir,
  184. destinationUrlPath: destPath,
  185. fileSize: fi.Size(),
  186. fileMode: fi.Mode(),
  187. uid: uid,
  188. gid: gid,
  189. }
  190. if mode.IsDir() {
  191. files, _ := ioutil.ReadDir(fileOrDir)
  192. println("checking directory", fileOrDir)
  193. for _, subFileOrDir := range files {
  194. if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), destPath+fi.Name()+"/", fileCopyTaskChan); err != nil {
  195. return err
  196. }
  197. }
  198. }
  199. return nil
  200. }
  201. type FileCopyWorker struct {
  202. options *CopyOptions
  203. filerHost string
  204. filerGrpcAddress string
  205. }
  206. func (worker *FileCopyWorker) copyFiles(fileCopyTaskChan chan FileCopyTask) error {
  207. for task := range fileCopyTaskChan {
  208. if err := worker.doEachCopy(task); err != nil {
  209. return err
  210. }
  211. }
  212. return nil
  213. }
  214. type FileCopyTask struct {
  215. sourceLocation string
  216. destinationUrlPath string
  217. fileSize int64
  218. fileMode os.FileMode
  219. uid uint32
  220. gid uint32
  221. }
  222. func (worker *FileCopyWorker) doEachCopy(task FileCopyTask) error {
  223. f, err := os.Open(task.sourceLocation)
  224. if err != nil {
  225. fmt.Printf("Failed to open file %s: %v\n", task.sourceLocation, err)
  226. if _, ok := err.(*os.PathError); ok {
  227. fmt.Printf("skipping %s\n", task.sourceLocation)
  228. return nil
  229. }
  230. return err
  231. }
  232. defer f.Close()
  233. // this is a regular file
  234. if *worker.options.include != "" {
  235. if ok, _ := filepath.Match(*worker.options.include, filepath.Base(task.sourceLocation)); !ok {
  236. return nil
  237. }
  238. }
  239. // find the chunk count
  240. chunkSize := int64(*worker.options.maxMB * 1024 * 1024)
  241. chunkCount := 1
  242. if chunkSize > 0 && task.fileSize > chunkSize {
  243. chunkCount = int(task.fileSize/chunkSize) + 1
  244. }
  245. if chunkCount == 1 {
  246. return worker.uploadFileAsOne(task, f)
  247. }
  248. return worker.uploadFileInChunks(task, f, chunkCount, chunkSize)
  249. }
  250. func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) error {
  251. // upload the file content
  252. fileName := filepath.Base(f.Name())
  253. var mimeType string
  254. var chunks []*filer_pb.FileChunk
  255. var assignResult *filer_pb.AssignVolumeResponse
  256. var assignError error
  257. if task.fileMode & os.ModeDir == 0 && task.fileSize > 0 {
  258. mimeType = detectMimeType(f)
  259. data, err := ioutil.ReadAll(f)
  260. if err != nil {
  261. return err
  262. }
  263. // assign a volume
  264. err = pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  265. request := &filer_pb.AssignVolumeRequest{
  266. Count: 1,
  267. Replication: *worker.options.replication,
  268. Collection: *worker.options.collection,
  269. TtlSec: worker.options.ttlSec,
  270. DiskType: *worker.options.diskType,
  271. Path: task.destinationUrlPath,
  272. }
  273. assignResult, assignError = client.AssignVolume(context.Background(), request)
  274. if assignError != nil {
  275. return fmt.Errorf("assign volume failure %v: %v", request, assignError)
  276. }
  277. if assignResult.Error != "" {
  278. return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error)
  279. }
  280. return nil
  281. })
  282. if err != nil {
  283. return fmt.Errorf("Failed to assign from %v: %v\n", worker.options.masters, err)
  284. }
  285. targetUrl := "http://" + assignResult.Url + "/" + assignResult.FileId
  286. uploadResult, err := operation.UploadData(targetUrl, fileName, worker.options.cipher, data, false, mimeType, nil, security.EncodedJwt(assignResult.Auth))
  287. if err != nil {
  288. return fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
  289. }
  290. if uploadResult.Error != "" {
  291. return fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
  292. }
  293. fmt.Printf("uploaded %s to %s\n", fileName, targetUrl)
  294. chunks = append(chunks, uploadResult.ToPbFileChunk(assignResult.FileId, 0))
  295. fmt.Printf("copied %s => http://%s%s%s\n", fileName, worker.filerHost, task.destinationUrlPath, fileName)
  296. }
  297. if err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  298. request := &filer_pb.CreateEntryRequest{
  299. Directory: task.destinationUrlPath,
  300. Entry: &filer_pb.Entry{
  301. Name: fileName,
  302. Attributes: &filer_pb.FuseAttributes{
  303. Crtime: time.Now().Unix(),
  304. Mtime: time.Now().Unix(),
  305. Gid: task.gid,
  306. Uid: task.uid,
  307. FileSize: uint64(task.fileSize),
  308. FileMode: uint32(task.fileMode),
  309. Mime: mimeType,
  310. Replication: *worker.options.replication,
  311. Collection: *worker.options.collection,
  312. TtlSec: worker.options.ttlSec,
  313. },
  314. Chunks: chunks,
  315. },
  316. }
  317. if err := filer_pb.CreateEntry(client, request); err != nil {
  318. return fmt.Errorf("update fh: %v", err)
  319. }
  320. return nil
  321. }); err != nil {
  322. return fmt.Errorf("upload data %v to http://%s%s%s: %v\n", fileName, worker.filerHost, task.destinationUrlPath, fileName, err)
  323. }
  324. return nil
  325. }
  326. func (worker *FileCopyWorker) uploadFileInChunks(task FileCopyTask, f *os.File, chunkCount int, chunkSize int64) error {
  327. fileName := filepath.Base(f.Name())
  328. mimeType := detectMimeType(f)
  329. chunksChan := make(chan *filer_pb.FileChunk, chunkCount)
  330. concurrentChunks := make(chan struct{}, *worker.options.concurrenctChunks)
  331. var wg sync.WaitGroup
  332. var uploadError error
  333. var collection, replication string
  334. fmt.Printf("uploading %s in %d chunks ...\n", fileName, chunkCount)
  335. for i := int64(0); i < int64(chunkCount) && uploadError == nil; i++ {
  336. wg.Add(1)
  337. concurrentChunks <- struct{}{}
  338. go func(i int64) {
  339. defer func() {
  340. wg.Done()
  341. <-concurrentChunks
  342. }()
  343. // assign a volume
  344. var assignResult *filer_pb.AssignVolumeResponse
  345. var assignError error
  346. err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  347. request := &filer_pb.AssignVolumeRequest{
  348. Count: 1,
  349. Replication: *worker.options.replication,
  350. Collection: *worker.options.collection,
  351. TtlSec: worker.options.ttlSec,
  352. DiskType: *worker.options.diskType,
  353. Path: task.destinationUrlPath + fileName,
  354. }
  355. assignResult, assignError = client.AssignVolume(context.Background(), request)
  356. if assignError != nil {
  357. return fmt.Errorf("assign volume failure %v: %v", request, assignError)
  358. }
  359. if assignResult.Error != "" {
  360. return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error)
  361. }
  362. return nil
  363. })
  364. if err != nil {
  365. fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err)
  366. }
  367. if err != nil {
  368. fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err)
  369. }
  370. targetUrl := "http://" + assignResult.Url + "/" + assignResult.FileId
  371. if collection == "" {
  372. collection = assignResult.Collection
  373. }
  374. if replication == "" {
  375. replication = assignResult.Replication
  376. }
  377. uploadResult, err, _ := operation.Upload(targetUrl, fileName+"-"+strconv.FormatInt(i+1, 10), worker.options.cipher, io.NewSectionReader(f, i*chunkSize, chunkSize), false, "", nil, security.EncodedJwt(assignResult.Auth))
  378. if err != nil {
  379. uploadError = fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
  380. return
  381. }
  382. if uploadResult.Error != "" {
  383. uploadError = fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
  384. return
  385. }
  386. chunksChan <- uploadResult.ToPbFileChunk(assignResult.FileId, i*chunkSize)
  387. fmt.Printf("uploaded %s-%d to %s [%d,%d)\n", fileName, i+1, targetUrl, i*chunkSize, i*chunkSize+int64(uploadResult.Size))
  388. }(i)
  389. }
  390. wg.Wait()
  391. close(chunksChan)
  392. var chunks []*filer_pb.FileChunk
  393. for chunk := range chunksChan {
  394. chunks = append(chunks, chunk)
  395. }
  396. if uploadError != nil {
  397. var fileIds []string
  398. for _, chunk := range chunks {
  399. fileIds = append(fileIds, chunk.FileId)
  400. }
  401. operation.DeleteFiles(func() string {
  402. return copy.masters[0]
  403. }, false, worker.options.grpcDialOption, fileIds)
  404. return uploadError
  405. }
  406. if err := pb.WithGrpcFilerClient(worker.filerGrpcAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  407. request := &filer_pb.CreateEntryRequest{
  408. Directory: task.destinationUrlPath,
  409. Entry: &filer_pb.Entry{
  410. Name: fileName,
  411. Attributes: &filer_pb.FuseAttributes{
  412. Crtime: time.Now().Unix(),
  413. Mtime: time.Now().Unix(),
  414. Gid: task.gid,
  415. Uid: task.uid,
  416. FileSize: uint64(task.fileSize),
  417. FileMode: uint32(task.fileMode),
  418. Mime: mimeType,
  419. Replication: replication,
  420. Collection: collection,
  421. TtlSec: worker.options.ttlSec,
  422. },
  423. Chunks: chunks,
  424. },
  425. }
  426. if err := filer_pb.CreateEntry(client, request); err != nil {
  427. return fmt.Errorf("update fh: %v", err)
  428. }
  429. return nil
  430. }); err != nil {
  431. return fmt.Errorf("upload data %v to http://%s%s%s: %v\n", fileName, worker.filerHost, task.destinationUrlPath, fileName, err)
  432. }
  433. fmt.Printf("copied %s => http://%s%s%s\n", fileName, worker.filerHost, task.destinationUrlPath, fileName)
  434. return nil
  435. }
  436. func detectMimeType(f *os.File) string {
  437. head := make([]byte, 512)
  438. f.Seek(0, io.SeekStart)
  439. n, err := f.Read(head)
  440. if err == io.EOF {
  441. return ""
  442. }
  443. if err != nil {
  444. fmt.Printf("read head of %v: %v\n", f.Name(), err)
  445. return ""
  446. }
  447. f.Seek(0, io.SeekStart)
  448. mimeType := http.DetectContentType(head[:n])
  449. if mimeType == "application/octet-stream" {
  450. return ""
  451. }
  452. return mimeType
  453. }