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.

499 lines
15 KiB

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