| @@ -36,7 +36,7 @@ func (svc *Service) CacheGC(msg *agtmq.CacheGC) (*agtmq.CacheGCResp, *mq.CodeMes | |||||
| return nil, mq.Failed(errorcode.OperationFailed, fmt.Sprintf("get shard store of storage %v: %v", msg.StorageID, err)) | return nil, mq.Failed(errorcode.OperationFailed, fmt.Sprintf("get shard store of storage %v: %v", msg.StorageID, err)) | ||||
| } | } | ||||
| err = store.Purge(msg.Avaiables) | |||||
| err = store.GC(msg.Avaiables) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, mq.Failed(errorcode.OperationFailed, fmt.Sprintf("purging cache: %v", err)) | return nil, mq.Failed(errorcode.OperationFailed, fmt.Sprintf("purging cache: %v", err)) | ||||
| } | } | ||||
| @@ -1,26 +1,15 @@ | |||||
| package mq | package mq | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| "io/fs" | |||||
| "os" | |||||
| "path/filepath" | |||||
| "strconv" | |||||
| "time" | "time" | ||||
| "github.com/samber/lo" | |||||
| "gitlink.org.cn/cloudream/common/consts/errorcode" | "gitlink.org.cn/cloudream/common/consts/errorcode" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | "gitlink.org.cn/cloudream/common/pkgs/logger" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | "gitlink.org.cn/cloudream/common/pkgs/mq" | ||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| mytask "gitlink.org.cn/cloudream/storage/agent/internal/task" | mytask "gitlink.org.cn/cloudream/storage/agent/internal/task" | ||||
| "gitlink.org.cn/cloudream/storage/common/consts" | |||||
| stgglb "gitlink.org.cn/cloudream/storage/common/globals" | stgglb "gitlink.org.cn/cloudream/storage/common/globals" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/iterator" | |||||
| stgmod "gitlink.org.cn/cloudream/storage/common/models" | |||||
| agtmq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/agent" | agtmq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/agent" | ||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | |||||
| "gitlink.org.cn/cloudream/storage/common/utils" | |||||
| ) | ) | ||||
| func (svc *Service) StartStorageLoadPackage(msg *agtmq.StartStorageLoadPackage) (*agtmq.StartStorageLoadPackageResp, *mq.CodeMessage) { | func (svc *Service) StartStorageLoadPackage(msg *agtmq.StartStorageLoadPackage) (*agtmq.StartStorageLoadPackageResp, *mq.CodeMessage) { | ||||
| @@ -68,70 +57,21 @@ func (svc *Service) WaitStorageLoadPackage(msg *agtmq.WaitStorageLoadPackage) (* | |||||
| func (svc *Service) StorageCheck(msg *agtmq.StorageCheck) (*agtmq.StorageCheckResp, *mq.CodeMessage) { | func (svc *Service) StorageCheck(msg *agtmq.StorageCheck) (*agtmq.StorageCheckResp, *mq.CodeMessage) { | ||||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | coorCli, err := stgglb.CoordinatorMQPool.Acquire() | ||||
| if err != nil { | if err != nil { | ||||
| return mq.ReplyOK(agtmq.NewStorageCheckResp( | |||||
| err.Error(), | |||||
| nil, | |||||
| )) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | } | ||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | defer stgglb.CoordinatorMQPool.Release(coorCli) | ||||
| // TODO UserID。应该设计两种接口,一种需要UserID,一种不需要。 | |||||
| getStg, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{msg.StorageID})) | |||||
| shared, err := svc.stgMgr.GetSharedStore(msg.StorageID) | |||||
| if err != nil { | if err != nil { | ||||
| return mq.ReplyOK(agtmq.NewStorageCheckResp( | |||||
| err.Error(), | |||||
| nil, | |||||
| )) | |||||
| } | |||||
| if getStg.Storages[0] == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage not found") | |||||
| } | |||||
| if getStg.Storages[0].Shared == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage has no shared storage") | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | } | ||||
| entries, err := os.ReadDir(utils.MakeStorageLoadDirectory(getStg.Storages[0].Shared.LoadBase)) | |||||
| loaded, err := shared.ListLoadedPackages() | |||||
| if err != nil { | if err != nil { | ||||
| logger.Warnf("list storage directory failed, err: %s", err.Error()) | |||||
| return mq.ReplyOK(agtmq.NewStorageCheckResp( | |||||
| err.Error(), | |||||
| nil, | |||||
| )) | |||||
| } | |||||
| var stgPkgs []model.StoragePackage | |||||
| userDirs := lo.Filter(entries, func(info fs.DirEntry, index int) bool { return info.IsDir() }) | |||||
| for _, dir := range userDirs { | |||||
| userIDInt, err := strconv.ParseInt(dir.Name(), 10, 64) | |||||
| if err != nil { | |||||
| logger.Warnf("parsing user id %s: %s", dir.Name(), err.Error()) | |||||
| continue | |||||
| } | |||||
| pkgDir := filepath.Join(utils.MakeStorageLoadDirectory(getStg.Storages[0].Shared.LoadBase), dir.Name()) | |||||
| pkgDirs, err := os.ReadDir(pkgDir) | |||||
| if err != nil { | |||||
| logger.Warnf("reading package dir %s: %s", pkgDir, err.Error()) | |||||
| continue | |||||
| } | |||||
| for _, pkg := range pkgDirs { | |||||
| pkgIDInt, err := strconv.ParseInt(pkg.Name(), 10, 64) | |||||
| if err != nil { | |||||
| logger.Warnf("parsing package dir %s: %s", pkg.Name(), err.Error()) | |||||
| continue | |||||
| } | |||||
| stgPkgs = append(stgPkgs, model.StoragePackage{ | |||||
| StorageID: msg.StorageID, | |||||
| PackageID: cdssdk.PackageID(pkgIDInt), | |||||
| UserID: cdssdk.UserID(userIDInt), | |||||
| }) | |||||
| } | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | } | ||||
| return mq.ReplyOK(agtmq.NewStorageCheckResp(consts.StorageDirectoryStateOK, stgPkgs)) | |||||
| return mq.ReplyOK(agtmq.NewStorageCheckResp(loaded)) | |||||
| } | } | ||||
| func (svc *Service) StorageGC(msg *agtmq.StorageGC) (*agtmq.StorageGCResp, *mq.CodeMessage) { | func (svc *Service) StorageGC(msg *agtmq.StorageGC) (*agtmq.StorageGCResp, *mq.CodeMessage) { | ||||
| @@ -141,121 +81,71 @@ func (svc *Service) StorageGC(msg *agtmq.StorageGC) (*agtmq.StorageGCResp, *mq.C | |||||
| } | } | ||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | defer stgglb.CoordinatorMQPool.Release(coorCli) | ||||
| // TODO UserID。应该设计两种接口,一种需要UserID,一种不需要。 | |||||
| getStg, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{msg.StorageID})) | |||||
| shared, err := svc.stgMgr.GetSharedStore(msg.StorageID) | |||||
| if err != nil { | if err != nil { | ||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | ||||
| } | } | ||||
| if getStg.Storages[0] == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage not found") | |||||
| } | |||||
| if getStg.Storages[0].Shared == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage has no shared storage") | |||||
| } | |||||
| entries, err := os.ReadDir(utils.MakeStorageLoadDirectory(getStg.Storages[0].Shared.LoadBase)) | |||||
| if err != nil { | |||||
| logger.Warnf("list storage directory failed, err: %s", err.Error()) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "list directory files failed") | |||||
| } | |||||
| // userID->pkgID->pkg | |||||
| userPkgs := make(map[string]map[string]bool) | |||||
| var loadeds []stgmod.LoadedPackageID | |||||
| for _, pkg := range msg.Packages { | for _, pkg := range msg.Packages { | ||||
| userIDStr := fmt.Sprintf("%d", pkg.UserID) | |||||
| pkgs, ok := userPkgs[userIDStr] | |||||
| if !ok { | |||||
| pkgs = make(map[string]bool) | |||||
| userPkgs[userIDStr] = pkgs | |||||
| } | |||||
| pkgIDStr := fmt.Sprintf("%d", pkg.PackageID) | |||||
| pkgs[pkgIDStr] = true | |||||
| loadeds = append(loadeds, stgmod.LoadedPackageID{ | |||||
| UserID: pkg.UserID, | |||||
| PackageID: pkg.PackageID, | |||||
| }) | |||||
| } | } | ||||
| userDirs := lo.Filter(entries, func(info fs.DirEntry, index int) bool { return info.IsDir() }) | |||||
| for _, dir := range userDirs { | |||||
| pkgMap, ok := userPkgs[dir.Name()] | |||||
| // 第一级目录名是UserID,先删除UserID在StoragePackage表里没出现过的文件夹 | |||||
| if !ok { | |||||
| rmPath := filepath.Join(utils.MakeStorageLoadDirectory(getStg.Storages[0].Shared.LoadBase), dir.Name()) | |||||
| err := os.RemoveAll(rmPath) | |||||
| if err != nil { | |||||
| logger.Warnf("removing user dir %s: %s", rmPath, err.Error()) | |||||
| } else { | |||||
| logger.Debugf("user dir %s removed by gc", rmPath) | |||||
| } | |||||
| continue | |||||
| } | |||||
| pkgDir := filepath.Join(utils.MakeStorageLoadDirectory(getStg.Storages[0].Shared.LoadBase), dir.Name()) | |||||
| // 遍历每个UserID目录的packages目录里的内容 | |||||
| pkgs, err := os.ReadDir(pkgDir) | |||||
| if err != nil { | |||||
| logger.Warnf("reading package dir %s: %s", pkgDir, err.Error()) | |||||
| continue | |||||
| } | |||||
| for _, pkg := range pkgs { | |||||
| if !pkgMap[pkg.Name()] { | |||||
| rmPath := filepath.Join(pkgDir, pkg.Name()) | |||||
| err := os.RemoveAll(rmPath) | |||||
| if err != nil { | |||||
| logger.Warnf("removing package dir %s: %s", rmPath, err.Error()) | |||||
| } else { | |||||
| logger.Debugf("package dir %s removed by gc", rmPath) | |||||
| } | |||||
| } | |||||
| } | |||||
| err = shared.PackageGC(loadeds) | |||||
| if err != nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | } | ||||
| return mq.ReplyOK(agtmq.RespStorageGC()) | return mq.ReplyOK(agtmq.RespStorageGC()) | ||||
| } | } | ||||
| func (svc *Service) StartStorageCreatePackage(msg *agtmq.StartStorageCreatePackage) (*agtmq.StartStorageCreatePackageResp, *mq.CodeMessage) { | func (svc *Service) StartStorageCreatePackage(msg *agtmq.StartStorageCreatePackage) (*agtmq.StartStorageCreatePackageResp, *mq.CodeMessage) { | ||||
| coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||||
| if err != nil { | |||||
| logger.Warnf("new coordinator client: %s", err.Error()) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "new coordinator client failed") | |||||
| } | |||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | |||||
| getStg, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{msg.StorageID})) | |||||
| if err != nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| } | |||||
| if getStg.Storages[0] == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage not found") | |||||
| } | |||||
| if getStg.Storages[0].Shared == nil { | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "storage has no shared storage") | |||||
| } | |||||
| fullPath := filepath.Clean(filepath.Join(getStg.Storages[0].Shared.LoadBase, msg.Path)) | |||||
| var uploadFilePathes []string | |||||
| err = filepath.WalkDir(fullPath, func(fname string, fi os.DirEntry, err error) error { | |||||
| if err != nil { | |||||
| return nil | |||||
| } | |||||
| if !fi.IsDir() { | |||||
| uploadFilePathes = append(uploadFilePathes, fname) | |||||
| } | |||||
| return nil | |||||
| }) | |||||
| if err != nil { | |||||
| logger.Warnf("opening directory %s: %s", fullPath, err.Error()) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "read directory failed") | |||||
| } | |||||
| objIter := iterator.NewUploadingObjectIterator(fullPath, uploadFilePathes) | |||||
| tsk := svc.taskManager.StartNew(mytask.NewCreatePackage(msg.UserID, msg.BucketID, msg.Name, objIter, msg.StorageAffinity)) | |||||
| return mq.ReplyOK(agtmq.NewStartStorageCreatePackageResp(tsk.ID())) | |||||
| return nil, mq.Failed(errorcode.OperationFailed, "not implemented") | |||||
| // coorCli, err := stgglb.CoordinatorMQPool.Acquire() | |||||
| // if err != nil { | |||||
| // logger.Warnf("new coordinator client: %s", err.Error()) | |||||
| // return nil, mq.Failed(errorcode.OperationFailed, "new coordinator client failed") | |||||
| // } | |||||
| // defer stgglb.CoordinatorMQPool.Release(coorCli) | |||||
| // getStg, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{msg.StorageID})) | |||||
| // if err != nil { | |||||
| // return nil, mq.Failed(errorcode.OperationFailed, err.Error()) | |||||
| // } | |||||
| // if getStg.Storages[0] == nil { | |||||
| // return nil, mq.Failed(errorcode.OperationFailed, "storage not found") | |||||
| // } | |||||
| // if getStg.Storages[0].Shared == nil { | |||||
| // return nil, mq.Failed(errorcode.OperationFailed, "storage has no shared storage") | |||||
| // } | |||||
| // fullPath := filepath.Clean(filepath.Join(getStg.Storages[0].Shared.LoadBase, msg.Path)) | |||||
| // var uploadFilePathes []string | |||||
| // err = filepath.WalkDir(fullPath, func(fname string, fi os.DirEntry, err error) error { | |||||
| // if err != nil { | |||||
| // return nil | |||||
| // } | |||||
| // if !fi.IsDir() { | |||||
| // uploadFilePathes = append(uploadFilePathes, fname) | |||||
| // } | |||||
| // return nil | |||||
| // }) | |||||
| // if err != nil { | |||||
| // logger.Warnf("opening directory %s: %s", fullPath, err.Error()) | |||||
| // return nil, mq.Failed(errorcode.OperationFailed, "read directory failed") | |||||
| // } | |||||
| // objIter := iterator.NewUploadingObjectIterator(fullPath, uploadFilePathes) | |||||
| // tsk := svc.taskManager.StartNew(mytask.NewCreatePackage(msg.UserID, msg.BucketID, msg.Name, objIter, msg.StorageAffinity)) | |||||
| // return mq.ReplyOK(agtmq.NewStartStorageCreatePackageResp(tsk.ID())) | |||||
| } | } | ||||
| func (svc *Service) WaitStorageCreatePackage(msg *agtmq.WaitStorageCreatePackage) (*agtmq.WaitStorageCreatePackageResp, *mq.CodeMessage) { | func (svc *Service) WaitStorageCreatePackage(msg *agtmq.WaitStorageCreatePackage) (*agtmq.WaitStorageCreatePackageResp, *mq.CodeMessage) { | ||||
| @@ -2,7 +2,6 @@ package task | |||||
| import ( | import ( | ||||
| "fmt" | "fmt" | ||||
| "io" | |||||
| "time" | "time" | ||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | "gitlink.org.cn/cloudream/common/pkgs/logger" | ||||
| @@ -74,16 +73,10 @@ func (t *CacheMovePackage) do(ctx TaskContext) error { | |||||
| } | } | ||||
| defer obj.File.Close() | defer obj.File.Close() | ||||
| writer := store.New() | |||||
| _, err = io.Copy(writer, obj.File) | |||||
| _, err = store.Create(obj.File) | |||||
| if err != nil { | if err != nil { | ||||
| writer.Abort() | |||||
| return fmt.Errorf("writing to store: %w", err) | return fmt.Errorf("writing to store: %w", err) | ||||
| } | } | ||||
| _, err = writer.Finish() | |||||
| if err != nil { | |||||
| return fmt.Errorf("finishing store: %w", err) | |||||
| } | |||||
| ctx.accessStat.AddAccessCounter(obj.Object.ObjectID, t.packageID, t.storageID, 1) | ctx.accessStat.AddAccessCounter(obj.Object.ObjectID, t.packageID, t.storageID, 1) | ||||
| } | } | ||||
| @@ -4,8 +4,6 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "math" | "math" | ||||
| "os" | |||||
| "path/filepath" | |||||
| "time" | "time" | ||||
| "github.com/samber/lo" | "github.com/samber/lo" | ||||
| @@ -23,7 +21,7 @@ import ( | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/ec" | "gitlink.org.cn/cloudream/storage/common/pkgs/ec" | ||||
| coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | coormq "gitlink.org.cn/cloudream/storage/common/pkgs/mq/coordinator" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | ||||
| "gitlink.org.cn/cloudream/storage/common/utils" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/utils" | |||||
| ) | ) | ||||
| type StorageLoadPackage struct { | type StorageLoadPackage struct { | ||||
| @@ -71,23 +69,11 @@ func (t *StorageLoadPackage) do(task *task.Task[TaskContext], ctx TaskContext) e | |||||
| } | } | ||||
| defer stgglb.CoordinatorMQPool.Release(coorCli) | defer stgglb.CoordinatorMQPool.Release(coorCli) | ||||
| getStgResp, err := coorCli.GetStorageDetails(coormq.ReqGetStorageDetails([]cdssdk.StorageID{t.storageID})) | |||||
| shared, err := ctx.stgMgr.GetSharedStore(t.storageID) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("request to coordinator: %w", err) | |||||
| return fmt.Errorf("get shared store of storage %v: %w", t.storageID, err) | |||||
| } | } | ||||
| if getStgResp.Storages[0] == nil { | |||||
| return fmt.Errorf("storage not found") | |||||
| } | |||||
| if getStgResp.Storages[0].Shared == nil { | |||||
| return fmt.Errorf("storage has shared storage") | |||||
| } | |||||
| t.PackagePath = utils.MakeLoadedPackagePath(t.userID, t.packageID) | t.PackagePath = utils.MakeLoadedPackagePath(t.userID, t.packageID) | ||||
| fullLocalPath := filepath.Join(getStgResp.Storages[0].Shared.LoadBase, t.PackagePath) | |||||
| if err = os.MkdirAll(fullLocalPath, 0755); err != nil { | |||||
| return fmt.Errorf("creating output directory: %w", err) | |||||
| } | |||||
| getObjectDetails, err := coorCli.GetPackageObjectDetails(coormq.ReqGetPackageObjectDetails(t.packageID)) | getObjectDetails, err := coorCli.GetPackageObjectDetails(coormq.ReqGetPackageObjectDetails(t.packageID)) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -113,7 +99,7 @@ func (t *StorageLoadPackage) do(task *task.Task[TaskContext], ctx TaskContext) e | |||||
| defer mutex.Unlock() | defer mutex.Unlock() | ||||
| for _, obj := range getObjectDetails.Objects { | for _, obj := range getObjectDetails.Objects { | ||||
| err := t.downloadOne(coorCli, shardstore, fullLocalPath, obj) | |||||
| err := t.downloadOne(coorCli, shardstore, shared, obj) | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -129,7 +115,7 @@ func (t *StorageLoadPackage) do(task *task.Task[TaskContext], ctx TaskContext) e | |||||
| return err | return err | ||||
| } | } | ||||
| func (t *StorageLoadPackage) downloadOne(coorCli *coormq.Client, shardStore types.ShardStore, dir string, obj stgmod.ObjectDetail) error { | |||||
| func (t *StorageLoadPackage) downloadOne(coorCli *coormq.Client, shardStore types.ShardStore, shared types.SharedStore, obj stgmod.ObjectDetail) error { | |||||
| var file io.ReadCloser | var file io.ReadCloser | ||||
| switch red := obj.Object.Redundancy.(type) { | switch red := obj.Object.Redundancy.(type) { | ||||
| @@ -160,20 +146,7 @@ func (t *StorageLoadPackage) downloadOne(coorCli *coormq.Client, shardStore type | |||||
| } | } | ||||
| defer file.Close() | defer file.Close() | ||||
| fullPath := filepath.Join(dir, obj.Object.Path) | |||||
| lastDirPath := filepath.Dir(fullPath) | |||||
| if err := os.MkdirAll(lastDirPath, 0755); err != nil { | |||||
| return fmt.Errorf("creating object last dir: %w", err) | |||||
| } | |||||
| outputFile, err := os.Create(fullPath) | |||||
| if err != nil { | |||||
| return fmt.Errorf("creating object file: %w", err) | |||||
| } | |||||
| defer outputFile.Close() | |||||
| if _, err := io.Copy(outputFile, file); err != nil { | |||||
| if _, err := shared.WritePackageObject(t.userID, t.packageID, obj.Object.Path, file); err != nil { | |||||
| return fmt.Errorf("writting object to file: %w", err) | return fmt.Errorf("writting object to file: %w", err) | ||||
| } | } | ||||
| @@ -146,3 +146,8 @@ const ( | |||||
| AliCloud = "AliCloud" | AliCloud = "AliCloud" | ||||
| SugonCloud = "SugonCloud" | SugonCloud = "SugonCloud" | ||||
| ) | ) | ||||
| type LoadedPackageID struct { | |||||
| UserID cdssdk.UserID | |||||
| PackageID cdssdk.PackageID | |||||
| } | |||||
| @@ -98,19 +98,11 @@ func (o *ShardWrite) Execute(ctx *exec.ExecContext, e *exec.Executor) error { | |||||
| } | } | ||||
| defer input.Stream.Close() | defer input.Stream.Close() | ||||
| writer := store.New() | |||||
| defer writer.Abort() | |||||
| _, err = io.Copy(writer, input.Stream) | |||||
| fileInfo, err := store.Create(input.Stream) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("writing file to shard store: %w", err) | return fmt.Errorf("writing file to shard store: %w", err) | ||||
| } | } | ||||
| fileInfo, err := writer.Finish() | |||||
| if err != nil { | |||||
| return fmt.Errorf("finishing writing file to shard store: %w", err) | |||||
| } | |||||
| e.PutVar(o.FileHash, &FileHashValue{ | e.PutVar(o.FileHash, &FileHashValue{ | ||||
| Hash: fileInfo.Hash, | Hash: fileInfo.Hash, | ||||
| }) | }) | ||||
| @@ -98,15 +98,7 @@ func (o *ShardWrite) Execute(ctx *exec.ExecContext, e *exec.Executor) error { | |||||
| } | } | ||||
| defer input.Stream.Close() | defer input.Stream.Close() | ||||
| writer := store.New() | |||||
| defer writer.Abort() | |||||
| _, err = io.Copy(writer, input.Stream) | |||||
| if err != nil { | |||||
| return fmt.Errorf("writing file to shard store: %w", err) | |||||
| } | |||||
| fileInfo, err := writer.Finish() | |||||
| fileInfo, err := store.Create(input.Stream) | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("finishing writing file to shard store: %w", err) | return fmt.Errorf("finishing writing file to shard store: %w", err) | ||||
| } | } | ||||
| @@ -4,6 +4,7 @@ import ( | |||||
| "gitlink.org.cn/cloudream/common/pkgs/mq" | "gitlink.org.cn/cloudream/common/pkgs/mq" | ||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | ||||
| stgmod "gitlink.org.cn/cloudream/storage/common/models" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" | "gitlink.org.cn/cloudream/storage/common/pkgs/db2/model" | ||||
| ) | ) | ||||
| @@ -96,8 +97,7 @@ type StorageCheck struct { | |||||
| } | } | ||||
| type StorageCheckResp struct { | type StorageCheckResp struct { | ||||
| mq.MessageBodyBase | mq.MessageBodyBase | ||||
| DirectoryState string `json:"directoryState"` | |||||
| Packages []model.StoragePackage `json:"packages"` | |||||
| Packages []stgmod.LoadedPackageID `json:"packages"` | |||||
| } | } | ||||
| func NewStorageCheck(storageID cdssdk.StorageID) *StorageCheck { | func NewStorageCheck(storageID cdssdk.StorageID) *StorageCheck { | ||||
| @@ -105,10 +105,9 @@ func NewStorageCheck(storageID cdssdk.StorageID) *StorageCheck { | |||||
| StorageID: storageID, | StorageID: storageID, | ||||
| } | } | ||||
| } | } | ||||
| func NewStorageCheckResp(dirState string, packages []model.StoragePackage) *StorageCheckResp { | |||||
| func NewStorageCheckResp(packages []stgmod.LoadedPackageID) *StorageCheckResp { | |||||
| return &StorageCheckResp{ | return &StorageCheckResp{ | ||||
| DirectoryState: dirState, | |||||
| Packages: packages, | |||||
| Packages: packages, | |||||
| } | } | ||||
| } | } | ||||
| func (client *Client) StorageCheck(msg *StorageCheck, opts ...mq.RequestOption) (*StorageCheckResp, error) { | func (client *Client) StorageCheck(msg *StorageCheck, opts ...mq.RequestOption) (*StorageCheckResp, error) { | ||||
| @@ -2,12 +2,14 @@ package local | |||||
| import ( | import ( | ||||
| "crypto/sha256" | "crypto/sha256" | ||||
| "encoding/hex" | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "io/fs" | "io/fs" | ||||
| "os" | "os" | ||||
| "path/filepath" | "path/filepath" | ||||
| "strings" | |||||
| "sync" | "sync" | ||||
| "time" | "time" | ||||
| @@ -15,13 +17,11 @@ import ( | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | ||||
| "gitlink.org.cn/cloudream/common/utils/io2" | "gitlink.org.cn/cloudream/common/utils/io2" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/utils" | |||||
| ) | ) | ||||
| const ( | const ( | ||||
| TempDir = "tmp" | TempDir = "tmp" | ||||
| BlocksDir = "blocks" | BlocksDir = "blocks" | ||||
| SvcName = "LocalShardStore" | |||||
| ) | ) | ||||
| type ShardStore struct { | type ShardStore struct { | ||||
| @@ -47,10 +47,12 @@ func NewShardStore(stg cdssdk.Storage, cfg cdssdk.LocalShardStorage) (*ShardStor | |||||
| } | } | ||||
| func (s *ShardStore) Start(ch *types.StorageEventChan) { | func (s *ShardStore) Start(ch *types.StorageEventChan) { | ||||
| s.getLogger().Infof("local shard store start, root: %v, max size: %v", s.cfg.Root, s.cfg.MaxSize) | |||||
| s.getLogger().Infof("component start, root: %v, max size: %v", s.cfg.Root, s.cfg.MaxSize) | |||||
| go func() { | go func() { | ||||
| removeTempTicker := time.NewTicker(time.Minute * 10) | removeTempTicker := time.NewTicker(time.Minute * 10) | ||||
| defer removeTempTicker.Stop() | |||||
| for { | for { | ||||
| select { | select { | ||||
| case <-removeTempTicker.C: | case <-removeTempTicker.C: | ||||
| @@ -83,18 +85,24 @@ func (s *ShardStore) removeUnusedTempFiles() { | |||||
| continue | continue | ||||
| } | } | ||||
| info, err := entry.Info() | |||||
| if err != nil { | |||||
| log.Warnf("get temp file %v info: %v", entry.Name(), err) | |||||
| continue | |||||
| } | |||||
| path := filepath.Join(s.cfg.Root, TempDir, entry.Name()) | path := filepath.Join(s.cfg.Root, TempDir, entry.Name()) | ||||
| err = os.Remove(path) | err = os.Remove(path) | ||||
| if err != nil { | if err != nil { | ||||
| log.Warnf("remove temp file %v: %v", path, err) | log.Warnf("remove temp file %v: %v", path, err) | ||||
| } else { | } else { | ||||
| log.Infof("remove unused temp file %v", path) | |||||
| log.Infof("remove unused temp file %v, size: %v, last mod time: %v", path, info.Size(), info.ModTime()) | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| func (s *ShardStore) Stop() { | func (s *ShardStore) Stop() { | ||||
| s.getLogger().Infof("local shard store stop") | |||||
| s.getLogger().Infof("component stop") | |||||
| select { | select { | ||||
| case s.done <- nil: | case s.done <- nil: | ||||
| @@ -102,7 +110,23 @@ func (s *ShardStore) Stop() { | |||||
| } | } | ||||
| } | } | ||||
| func (s *ShardStore) New() types.ShardWriter { | |||||
| func (s *ShardStore) Create(stream io.Reader) (types.FileInfo, error) { | |||||
| file, err := s.createTempFile() | |||||
| if err != nil { | |||||
| return types.FileInfo{}, err | |||||
| } | |||||
| size, hash, err := s.writeTempFile(file, stream) | |||||
| if err != nil { | |||||
| // Name是文件完整路径 | |||||
| s.onCreateFailed(file.Name()) | |||||
| return types.FileInfo{}, err | |||||
| } | |||||
| return s.onCreateFinished(file.Name(), size, hash) | |||||
| } | |||||
| func (s *ShardStore) createTempFile() (*os.File, error) { | |||||
| s.lock.Lock() | s.lock.Lock() | ||||
| defer s.lock.Unlock() | defer s.lock.Unlock() | ||||
| @@ -110,25 +134,107 @@ func (s *ShardStore) New() types.ShardWriter { | |||||
| err := os.MkdirAll(tmpDir, 0755) | err := os.MkdirAll(tmpDir, 0755) | ||||
| if err != nil { | if err != nil { | ||||
| return utils.ErrorShardWriter(err) | |||||
| s.lock.Unlock() | |||||
| return nil, err | |||||
| } | } | ||||
| file, err := os.CreateTemp(tmpDir, "tmp-*") | file, err := os.CreateTemp(tmpDir, "tmp-*") | ||||
| if err != nil { | if err != nil { | ||||
| return utils.ErrorShardWriter(err) | |||||
| s.lock.Unlock() | |||||
| return nil, err | |||||
| } | } | ||||
| s.workingTempFiles[filepath.Base(file.Name())] = true | s.workingTempFiles[filepath.Base(file.Name())] = true | ||||
| return &ShardWriter{ | |||||
| path: file.Name(), // file.Name 包含tmpDir路径 | |||||
| file: file, | |||||
| hasher: sha256.New(), | |||||
| owner: s, | |||||
| return file, nil | |||||
| } | |||||
| func (s *ShardStore) writeTempFile(file *os.File, stream io.Reader) (int64, cdssdk.FileHash, error) { | |||||
| buf := make([]byte, 32*1024) | |||||
| size := int64(0) | |||||
| hasher := sha256.New() | |||||
| for { | |||||
| n, err := stream.Read(buf) | |||||
| if n > 0 { | |||||
| size += int64(n) | |||||
| io2.WriteAll(hasher, buf[:n]) | |||||
| err := io2.WriteAll(file, buf[:n]) | |||||
| if err != nil { | |||||
| return 0, "", err | |||||
| } | |||||
| } | |||||
| if err == io.EOF { | |||||
| break | |||||
| } | |||||
| if err != nil { | |||||
| return 0, "", err | |||||
| } | |||||
| } | } | ||||
| h := hasher.Sum(nil) | |||||
| return size, cdssdk.FileHash(strings.ToUpper(hex.EncodeToString(h))), nil | |||||
| } | } | ||||
| // 使用F函数创建Option对象 | |||||
| func (s *ShardStore) onCreateFinished(tempFilePath string, size int64, hash cdssdk.FileHash) (types.FileInfo, error) { | |||||
| s.lock.Lock() | |||||
| defer s.lock.Unlock() | |||||
| defer delete(s.workingTempFiles, filepath.Base(tempFilePath)) | |||||
| log := s.getLogger() | |||||
| log.Debugf("write file %v finished, size: %v, hash: %v", tempFilePath, size, hash) | |||||
| blockDir := s.getFileDirFromHash(hash) | |||||
| err := os.MkdirAll(blockDir, 0755) | |||||
| if err != nil { | |||||
| s.removeTempFile(tempFilePath) | |||||
| log.Warnf("make block dir %v: %v", blockDir, err) | |||||
| return types.FileInfo{}, fmt.Errorf("making block dir: %w", err) | |||||
| } | |||||
| newPath := filepath.Join(blockDir, string(hash)) | |||||
| _, err = os.Stat(newPath) | |||||
| if os.IsNotExist(err) { | |||||
| err = os.Rename(tempFilePath, newPath) | |||||
| if err != nil { | |||||
| s.removeTempFile(tempFilePath) | |||||
| log.Warnf("rename %v to %v: %v", tempFilePath, newPath, err) | |||||
| return types.FileInfo{}, fmt.Errorf("rename file: %w", err) | |||||
| } | |||||
| } else if err != nil { | |||||
| s.removeTempFile(tempFilePath) | |||||
| log.Warnf("get file %v stat: %v", newPath, err) | |||||
| return types.FileInfo{}, fmt.Errorf("get file stat: %w", err) | |||||
| } else { | |||||
| s.removeTempFile(tempFilePath) | |||||
| } | |||||
| return types.FileInfo{ | |||||
| Hash: hash, | |||||
| Size: size, | |||||
| Description: tempFilePath, | |||||
| }, nil | |||||
| } | |||||
| func (s *ShardStore) onCreateFailed(tempFilePath string) { | |||||
| s.lock.Lock() | |||||
| defer s.lock.Unlock() | |||||
| s.getLogger().Debugf("writting file %v aborted", tempFilePath) | |||||
| s.removeTempFile(tempFilePath) | |||||
| delete(s.workingTempFiles, filepath.Base(tempFilePath)) | |||||
| } | |||||
| func (s *ShardStore) removeTempFile(path string) { | |||||
| err := os.Remove(path) | |||||
| if err != nil { | |||||
| s.getLogger().Warnf("removing temp file %v: %v", path, err) | |||||
| } | |||||
| } | |||||
| // 使用NewOpen函数创建Option对象 | |||||
| func (s *ShardStore) Open(opt types.OpenOption) (io.ReadCloser, error) { | func (s *ShardStore) Open(opt types.OpenOption) (io.ReadCloser, error) { | ||||
| s.lock.Lock() | s.lock.Lock() | ||||
| defer s.lock.Unlock() | defer s.lock.Unlock() | ||||
| @@ -212,22 +318,46 @@ func (s *ShardStore) ListAll() ([]types.FileInfo, error) { | |||||
| return infos, nil | return infos, nil | ||||
| } | } | ||||
| func (s *ShardStore) Purge(removes []cdssdk.FileHash) error { | |||||
| func (s *ShardStore) GC(avaiables []cdssdk.FileHash) error { | |||||
| s.lock.Lock() | s.lock.Lock() | ||||
| defer s.lock.Unlock() | defer s.lock.Unlock() | ||||
| avais := make(map[cdssdk.FileHash]bool) | |||||
| for _, hash := range avaiables { | |||||
| avais[hash] = true | |||||
| } | |||||
| cnt := 0 | cnt := 0 | ||||
| for _, hash := range removes { | |||||
| fileName := string(hash) | |||||
| blockDir := filepath.Join(s.cfg.Root, BlocksDir) | |||||
| err := filepath.WalkDir(blockDir, func(path string, d fs.DirEntry, err error) error { | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| path := filepath.Join(s.cfg.Root, BlocksDir, fileName[:2], fileName) | |||||
| err := os.Remove(path) | |||||
| if d.IsDir() { | |||||
| return nil | |||||
| } | |||||
| info, err := d.Info() | |||||
| if err != nil { | if err != nil { | ||||
| s.getLogger().Warnf("remove file %v: %v", path, err) | |||||
| } else { | |||||
| cnt++ | |||||
| return err | |||||
| } | } | ||||
| fileHash := cdssdk.FileHash(filepath.Base(info.Name())) | |||||
| if !avais[fileHash] { | |||||
| err = os.Remove(path) | |||||
| if err != nil { | |||||
| s.getLogger().Warnf("remove file %v: %v", path, err) | |||||
| } else { | |||||
| cnt++ | |||||
| } | |||||
| } | |||||
| return nil | |||||
| }) | |||||
| if err != nil && !errors.Is(err, os.ErrNotExist) { | |||||
| return err | |||||
| } | } | ||||
| s.getLogger().Infof("purge %d files", cnt) | s.getLogger().Infof("purge %d files", cnt) | ||||
| @@ -243,66 +373,8 @@ func (s *ShardStore) Stats() types.Stats { | |||||
| } | } | ||||
| } | } | ||||
| func (s *ShardStore) onWritterAbort(w *ShardWriter) { | |||||
| s.lock.Lock() | |||||
| defer s.lock.Unlock() | |||||
| s.getLogger().Debugf("writting file %v aborted", w.path) | |||||
| s.removeTempFile(w.path) | |||||
| delete(s.workingTempFiles, filepath.Base(w.path)) | |||||
| } | |||||
| func (s *ShardStore) onWritterFinish(w *ShardWriter, hash cdssdk.FileHash) (types.FileInfo, error) { | |||||
| s.lock.Lock() | |||||
| defer s.lock.Unlock() | |||||
| defer delete(s.workingTempFiles, filepath.Base(w.path)) | |||||
| log := s.getLogger() | |||||
| log.Debugf("write file %v finished, size: %v, hash: %v", w.path, w.size, hash) | |||||
| blockDir := s.getFileDirFromHash(hash) | |||||
| err := os.MkdirAll(blockDir, 0755) | |||||
| if err != nil { | |||||
| s.removeTempFile(w.path) | |||||
| log.Warnf("make block dir %v: %v", blockDir, err) | |||||
| return types.FileInfo{}, fmt.Errorf("making block dir: %w", err) | |||||
| } | |||||
| newPath := filepath.Join(blockDir, string(hash)) | |||||
| _, err = os.Stat(newPath) | |||||
| if os.IsNotExist(err) { | |||||
| err = os.Rename(w.path, newPath) | |||||
| if err != nil { | |||||
| s.removeTempFile(w.path) | |||||
| log.Warnf("rename %v to %v: %v", w.path, newPath, err) | |||||
| return types.FileInfo{}, fmt.Errorf("rename file: %w", err) | |||||
| } | |||||
| } else if err != nil { | |||||
| s.removeTempFile(w.path) | |||||
| log.Warnf("get file %v stat: %v", newPath, err) | |||||
| return types.FileInfo{}, fmt.Errorf("get file stat: %w", err) | |||||
| } else { | |||||
| s.removeTempFile(w.path) | |||||
| } | |||||
| return types.FileInfo{ | |||||
| Hash: hash, | |||||
| Size: w.size, | |||||
| Description: w.path, | |||||
| }, nil | |||||
| } | |||||
| func (s *ShardStore) removeTempFile(path string) { | |||||
| err := os.Remove(path) | |||||
| if err != nil { | |||||
| s.getLogger().Warnf("removing temp file %v: %v", path, err) | |||||
| } | |||||
| } | |||||
| func (s *ShardStore) getLogger() logger.Logger { | func (s *ShardStore) getLogger() logger.Logger { | ||||
| return logger.WithField("S", SvcName).WithField("Storage", s.stg.String()) | |||||
| return logger.WithField("ShardStore", "Local").WithField("Storage", s.stg.String()) | |||||
| } | } | ||||
| func (s *ShardStore) getFileDirFromHash(hash cdssdk.FileHash) string { | func (s *ShardStore) getFileDirFromHash(hash cdssdk.FileHash) string { | ||||
| @@ -0,0 +1,225 @@ | |||||
| package local | |||||
| import ( | |||||
| "fmt" | |||||
| "io" | |||||
| "io/fs" | |||||
| "os" | |||||
| "path/filepath" | |||||
| "strconv" | |||||
| "github.com/samber/lo" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/logger" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| stgmod "gitlink.org.cn/cloudream/storage/common/models" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | |||||
| ) | |||||
| type SharedStore struct { | |||||
| stg cdssdk.Storage | |||||
| cfg cdssdk.LocalSharedStorage | |||||
| // lock sync.Mutex | |||||
| } | |||||
| func NewSharedStore(stg cdssdk.Storage, cfg cdssdk.LocalSharedStorage) (*SharedStore, error) { | |||||
| _, ok := stg.Address.(*cdssdk.LocalStorageAddress) | |||||
| if !ok { | |||||
| return nil, fmt.Errorf("storage address(%T) is not local", stg) | |||||
| } | |||||
| return &SharedStore{ | |||||
| stg: stg, | |||||
| cfg: cfg, | |||||
| }, nil | |||||
| } | |||||
| func (s *SharedStore) Start(ch *types.StorageEventChan) { | |||||
| s.getLogger().Infof("component start, LoadBase: %v", s.cfg.LoadBase) | |||||
| } | |||||
| func (s *SharedStore) Stop() { | |||||
| s.getLogger().Infof("component stop") | |||||
| } | |||||
| func (s *SharedStore) WritePackageObject(userID cdssdk.UserID, pkgID cdssdk.PackageID, path string, stream io.Reader) (string, error) { | |||||
| relaPath := filepath.Join(fmt.Sprintf("%v", userID), fmt.Sprintf("%v", pkgID), path) | |||||
| fullPath := filepath.Join(s.cfg.LoadBase, relaPath) | |||||
| err := os.MkdirAll(filepath.Dir(fullPath), 0755) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| f, err := os.Create(fullPath) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| defer f.Close() | |||||
| _, err = io.Copy(f, stream) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| return filepath.ToSlash(relaPath), nil | |||||
| } | |||||
| func (s *SharedStore) ListLoadedPackages() ([]stgmod.LoadedPackageID, error) { | |||||
| entries, err := os.ReadDir(s.cfg.LoadBase) | |||||
| if os.IsNotExist(err) { | |||||
| return nil, nil | |||||
| } | |||||
| if err != nil { | |||||
| s.getLogger().Warnf("list package directory: %v", err) | |||||
| return nil, err | |||||
| } | |||||
| var loadeds []stgmod.LoadedPackageID | |||||
| for _, e := range entries { | |||||
| if !e.IsDir() { | |||||
| continue | |||||
| } | |||||
| uid, err := strconv.ParseInt(e.Name(), 10, 64) | |||||
| if err != nil { | |||||
| continue | |||||
| } | |||||
| userID := cdssdk.UserID(uid) | |||||
| pkgs, err := s.listUserPackages(userID, fmt.Sprintf("%v", userID)) | |||||
| if err != nil { | |||||
| continue | |||||
| } | |||||
| loadeds = append(loadeds, pkgs...) | |||||
| } | |||||
| return loadeds, nil | |||||
| } | |||||
| func (s *SharedStore) listUserPackages(userID cdssdk.UserID, userIDStr string) ([]stgmod.LoadedPackageID, error) { | |||||
| userDir := filepath.Join(s.cfg.LoadBase, userIDStr) | |||||
| entries, err := os.ReadDir(userDir) | |||||
| if os.IsNotExist(err) { | |||||
| return nil, nil | |||||
| } | |||||
| if err != nil { | |||||
| s.getLogger().Warnf("list package directory: %v", err) | |||||
| return nil, err | |||||
| } | |||||
| var pkgs []stgmod.LoadedPackageID | |||||
| for _, e := range entries { | |||||
| if !e.IsDir() { | |||||
| continue | |||||
| } | |||||
| pkgID, err := strconv.ParseInt(e.Name(), 10, 64) | |||||
| if err != nil { | |||||
| continue | |||||
| } | |||||
| pkgs = append(pkgs, stgmod.LoadedPackageID{ | |||||
| UserID: userID, | |||||
| PackageID: cdssdk.PackageID(pkgID), | |||||
| }) | |||||
| } | |||||
| return pkgs, nil | |||||
| } | |||||
| func (s *SharedStore) PackageGC(avaiables []stgmod.LoadedPackageID) error { | |||||
| log := s.getLogger() | |||||
| entries, err := os.ReadDir(s.cfg.LoadBase) | |||||
| if err != nil { | |||||
| log.Warnf("list storage directory: %s", err.Error()) | |||||
| return err | |||||
| } | |||||
| // userID->pkgID->pkg | |||||
| userPkgs := make(map[string]map[string]bool) | |||||
| for _, pkg := range avaiables { | |||||
| userIDStr := fmt.Sprintf("%v", pkg.UserID) | |||||
| pkgs, ok := userPkgs[userIDStr] | |||||
| if !ok { | |||||
| pkgs = make(map[string]bool) | |||||
| userPkgs[userIDStr] = pkgs | |||||
| } | |||||
| pkgIDStr := fmt.Sprintf("%v", pkg.PackageID) | |||||
| pkgs[pkgIDStr] = true | |||||
| } | |||||
| userDirs := lo.Filter(entries, func(info fs.DirEntry, index int) bool { return info.IsDir() }) | |||||
| for _, dir := range userDirs { | |||||
| pkgMap, ok := userPkgs[dir.Name()] | |||||
| // 第一级目录名是UserID,先删除UserID在StoragePackage表里没出现过的文件夹 | |||||
| if !ok { | |||||
| rmPath := filepath.Join(s.cfg.LoadBase, dir.Name()) | |||||
| err := os.RemoveAll(rmPath) | |||||
| if err != nil { | |||||
| log.Warnf("removing user dir %s: %s", rmPath, err.Error()) | |||||
| } else { | |||||
| log.Debugf("user dir %s removed by gc", rmPath) | |||||
| } | |||||
| continue | |||||
| } | |||||
| pkgDir := filepath.Join(s.cfg.LoadBase, dir.Name()) | |||||
| // 遍历每个UserID目录的packages目录里的内容 | |||||
| pkgs, err := os.ReadDir(pkgDir) | |||||
| if err != nil { | |||||
| log.Warnf("reading package dir %s: %s", pkgDir, err.Error()) | |||||
| continue | |||||
| } | |||||
| for _, pkg := range pkgs { | |||||
| if !pkgMap[pkg.Name()] { | |||||
| rmPath := filepath.Join(pkgDir, pkg.Name()) | |||||
| err := os.RemoveAll(rmPath) | |||||
| if err != nil { | |||||
| log.Warnf("removing package dir %s: %s", rmPath, err.Error()) | |||||
| } else { | |||||
| log.Debugf("package dir %s removed by gc", rmPath) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func (s *SharedStore) getLogger() logger.Logger { | |||||
| return logger.WithField("SharedStore", "Local").WithField("Storage", s.stg.String()) | |||||
| } | |||||
| type PackageWriter struct { | |||||
| pkgRoot string | |||||
| fullDirPath string | |||||
| } | |||||
| func (w *PackageWriter) Root() string { | |||||
| return w.pkgRoot | |||||
| } | |||||
| func (w *PackageWriter) Write(path string, stream io.Reader) (string, error) { | |||||
| fullFilePath := filepath.Join(w.fullDirPath, path) | |||||
| err := os.MkdirAll(filepath.Dir(fullFilePath), 0755) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| f, err := os.Create(fullFilePath) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| defer f.Close() | |||||
| _, err = io.Copy(f, stream) | |||||
| if err != nil { | |||||
| return "", err | |||||
| } | |||||
| return filepath.ToSlash(filepath.Join(w.pkgRoot, path)), nil | |||||
| } | |||||
| @@ -1,66 +0,0 @@ | |||||
| package local | |||||
| import ( | |||||
| "encoding/hex" | |||||
| "fmt" | |||||
| "hash" | |||||
| "os" | |||||
| "strings" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | |||||
| ) | |||||
| type ShardWriter struct { | |||||
| path string | |||||
| file *os.File | |||||
| hasher hash.Hash | |||||
| size int64 | |||||
| closed bool | |||||
| owner *ShardStore | |||||
| } | |||||
| func (w *ShardWriter) Write(data []byte) (int, error) { | |||||
| n, err := w.file.Write(data) | |||||
| if err != nil { | |||||
| return 0, err | |||||
| } | |||||
| w.hasher.Write(data[:n]) | |||||
| w.size += int64(n) | |||||
| return n, nil | |||||
| } | |||||
| // 取消写入 | |||||
| func (w *ShardWriter) Abort() error { | |||||
| if w.closed { | |||||
| return nil | |||||
| } | |||||
| w.closed = true | |||||
| err := w.file.Close() | |||||
| w.owner.onWritterAbort(w) | |||||
| return err | |||||
| } | |||||
| // 结束写入,获得文件哈希值 | |||||
| func (w *ShardWriter) Finish() (types.FileInfo, error) { | |||||
| if w.closed { | |||||
| return types.FileInfo{}, fmt.Errorf("stream closed") | |||||
| } | |||||
| w.closed = true | |||||
| err := w.file.Close() | |||||
| if err != nil { | |||||
| w.owner.onWritterAbort(w) | |||||
| return types.FileInfo{}, err | |||||
| } | |||||
| sum := w.hasher.Sum(nil) | |||||
| info, err := w.owner.onWritterFinish(w, cdssdk.FileHash(strings.ToUpper(hex.EncodeToString(sum)))) | |||||
| if err != nil { | |||||
| // 无需再调onWritterAbort, onWritterFinish会处理 | |||||
| return types.FileInfo{}, err | |||||
| } | |||||
| return info, nil | |||||
| } | |||||
| @@ -1,10 +1,27 @@ | |||||
| package mgr | package mgr | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| stgmod "gitlink.org.cn/cloudream/storage/common/models" | stgmod "gitlink.org.cn/cloudream/storage/common/models" | ||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/local" | |||||
| "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | ||||
| ) | ) | ||||
| func createSharedStore(detail stgmod.StorageDetail, ch *types.StorageEventChan, stg *storage) error { | func createSharedStore(detail stgmod.StorageDetail, ch *types.StorageEventChan, stg *storage) error { | ||||
| return nil | |||||
| switch confg := detail.Storage.SharedStore.(type) { | |||||
| case *cdssdk.LocalSharedStorage: | |||||
| store, err := local.NewSharedStore(detail.Storage, *confg) | |||||
| if err != nil { | |||||
| return fmt.Errorf("new local shard store: %v", err) | |||||
| } | |||||
| store.Start(ch) | |||||
| stg.Shared = store | |||||
| return nil | |||||
| default: | |||||
| return fmt.Errorf("unsupported shard store type: %T", confg) | |||||
| } | |||||
| } | } | ||||
| @@ -24,16 +24,16 @@ type StoreEvent interface { | |||||
| type ShardStore interface { | type ShardStore interface { | ||||
| StorageComponent | StorageComponent | ||||
| // 准备写入一个新文件,写入后获得FileHash | |||||
| New() ShardWriter | |||||
| // 写入一个新文件,写入后获得FileHash | |||||
| Create(stream io.Reader) (FileInfo, error) | |||||
| // 使用F函数创建Option对象 | // 使用F函数创建Option对象 | ||||
| Open(opt OpenOption) (io.ReadCloser, error) | Open(opt OpenOption) (io.ReadCloser, error) | ||||
| // 获得指定文件信息 | // 获得指定文件信息 | ||||
| Info(fileHash cdssdk.FileHash) (FileInfo, error) | Info(fileHash cdssdk.FileHash) (FileInfo, error) | ||||
| // 获取所有文件信息,尽量保证操作是原子的 | // 获取所有文件信息,尽量保证操作是原子的 | ||||
| ListAll() ([]FileInfo, error) | ListAll() ([]FileInfo, error) | ||||
| // 删除指定的文件 | |||||
| Purge(removes []cdssdk.FileHash) error | |||||
| // 垃圾清理。只保留availables中的文件,删除其他文件 | |||||
| GC(avaiables []cdssdk.FileHash) error | |||||
| // 获得存储系统信息 | // 获得存储系统信息 | ||||
| Stats() Stats | Stats() Stats | ||||
| } | } | ||||
| @@ -63,14 +63,6 @@ type Stats struct { | |||||
| Description string | Description string | ||||
| } | } | ||||
| type ShardWriter interface { | |||||
| io.Writer | |||||
| // 取消写入。要求允许在调用了Finish之后再调用此函数,且此时不应该有任何影响,方便使用defer语句 | |||||
| Abort() error | |||||
| // 结束写入,获得文件哈希值 | |||||
| Finish() (FileInfo, error) | |||||
| } | |||||
| type OpenOption struct { | type OpenOption struct { | ||||
| FileHash cdssdk.FileHash | FileHash cdssdk.FileHash | ||||
| Offset int64 | Offset int64 | ||||
| @@ -1,5 +1,18 @@ | |||||
| package types | package types | ||||
| import ( | |||||
| "io" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| stgmod "gitlink.org.cn/cloudream/storage/common/models" | |||||
| ) | |||||
| type SharedStore interface { | type SharedStore interface { | ||||
| StorageComponent | StorageComponent | ||||
| // 写入一个文件到Package的调度目录下,返回值为文件路径:userID/pkgID/path | |||||
| WritePackageObject(userID cdssdk.UserID, pkgID cdssdk.PackageID, path string, stream io.Reader) (string, error) | |||||
| // 获取所有已加载的Package信息 | |||||
| ListLoadedPackages() ([]stgmod.LoadedPackageID, error) | |||||
| // 垃圾回收,删除过期的Package | |||||
| PackageGC(avaiables []stgmod.LoadedPackageID) error | |||||
| } | } | ||||
| @@ -1,26 +1,12 @@ | |||||
| package utils | package utils | ||||
| import "gitlink.org.cn/cloudream/storage/common/pkgs/storage/types" | |||||
| import ( | |||||
| "path/filepath" | |||||
| "strconv" | |||||
| type errorShardWriter struct { | |||||
| err error | |||||
| } | |||||
| func (w *errorShardWriter) Write(data []byte) (int, error) { | |||||
| return 0, w.err | |||||
| } | |||||
| // 取消写入。要求允许在调用了Finish之后再调用此函数,且此时不应该有任何影响。 | |||||
| // 方便defer机制 | |||||
| func (w *errorShardWriter) Abort() error { | |||||
| return w.err | |||||
| } | |||||
| // 结束写入,获得文件哈希值 | |||||
| func (w *errorShardWriter) Finish() (types.FileInfo, error) { | |||||
| return types.FileInfo{}, w.err | |||||
| } | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| ) | |||||
| func ErrorShardWriter(err error) types.ShardWriter { | |||||
| return &errorShardWriter{err: err} | |||||
| func MakeLoadedPackagePath(userID cdssdk.UserID, packageID cdssdk.PackageID) string { | |||||
| return filepath.Join("packages", strconv.FormatInt(int64(userID), 10), strconv.FormatInt(int64(packageID), 10)) | |||||
| } | } | ||||
| @@ -2,15 +2,8 @@ package utils | |||||
| import ( | import ( | ||||
| "path/filepath" | "path/filepath" | ||||
| "strconv" | |||||
| cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" | |||||
| ) | ) | ||||
| func MakeLoadedPackagePath(userID cdssdk.UserID, packageID cdssdk.PackageID) string { | |||||
| return filepath.Join("packages", strconv.FormatInt(int64(userID), 10), strconv.FormatInt(int64(packageID), 10)) | |||||
| } | |||||
| func MakeStorageLoadDirectory(stgDir string) string { | func MakeStorageLoadDirectory(stgDir string) string { | ||||
| return filepath.Join(stgDir, "packages") | return filepath.Join(stgDir, "packages") | ||||
| } | } | ||||