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.

507 lines
15 KiB

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