You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

user_space.go 9.7 kB

5 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. package services
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/samber/lo"
  7. "gitlink.org.cn/cloudream/common/pkgs/logger"
  8. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec"
  9. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  10. "gorm.io/gorm"
  11. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  12. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy"
  13. cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1"
  14. "gitlink.org.cn/cloudream/jcs-pub/common/ecode"
  15. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  16. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  17. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2"
  18. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  19. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/publock/reqbuilder"
  20. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/factory"
  21. )
  22. type UserSpaceService struct {
  23. *Service
  24. }
  25. func (svc *Service) UserSpaceSvc() *UserSpaceService {
  26. return &UserSpaceService{Service: svc}
  27. }
  28. func (svc *UserSpaceService) Get(userspaceID jcstypes.UserSpaceID) (jcstypes.UserSpace, error) {
  29. return svc.DB.UserSpace().GetByID(svc.DB.DefCtx(), userspaceID)
  30. }
  31. func (svc *UserSpaceService) GetByName(name string) (jcstypes.UserSpace, error) {
  32. return svc.DB.UserSpace().GetByName(svc.DB.DefCtx(), name)
  33. }
  34. func (svc *UserSpaceService) GetAll() ([]jcstypes.UserSpace, error) {
  35. return svc.DB.UserSpace().GetAll(svc.DB.DefCtx())
  36. }
  37. func (svc *UserSpaceService) Create(req cliapi.UserSpaceCreate) (*cliapi.UserSpaceCreateResp, *ecode.CodeError) {
  38. db2 := svc.DB
  39. space, err := db.DoTx01(db2, func(tx db.SQLContext) (jcstypes.UserSpace, error) {
  40. space, err := db2.UserSpace().GetByName(tx, req.Name)
  41. if err == nil {
  42. return jcstypes.UserSpace{}, gorm.ErrDuplicatedKey
  43. }
  44. if err != gorm.ErrRecordNotFound {
  45. return jcstypes.UserSpace{}, err
  46. }
  47. space = jcstypes.UserSpace{
  48. Name: req.Name,
  49. Storage: req.Storage,
  50. Credential: req.Credential,
  51. ShardStore: req.ShardStore,
  52. Features: req.Features,
  53. WorkingDir: jcstypes.PathFromJcsPathString(req.WorkingDir),
  54. Revision: 0,
  55. }
  56. err = db2.UserSpace().Create(tx, &space)
  57. if err != nil {
  58. return jcstypes.UserSpace{}, err
  59. }
  60. return space, nil
  61. })
  62. if err == gorm.ErrDuplicatedKey {
  63. return nil, ecode.New(ecode.DataExists, "user space name already exists")
  64. }
  65. if err != nil {
  66. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  67. }
  68. return &cliapi.UserSpaceCreateResp{UserSpace: space}, nil
  69. }
  70. func (svc *UserSpaceService) Update(req cliapi.UserSpaceUpdate) (*cliapi.UserSpaceUpdateResp, *ecode.CodeError) {
  71. db2 := svc.DB
  72. space, err := db.DoTx01(db2, func(tx db.SQLContext) (jcstypes.UserSpace, error) {
  73. space, err := db2.UserSpace().GetByID(tx, req.UserSpaceID)
  74. if err != nil {
  75. return jcstypes.UserSpace{}, err
  76. }
  77. if space.Name != req.Name {
  78. _, err = db2.UserSpace().GetByName(tx, req.Name)
  79. if err == nil {
  80. return jcstypes.UserSpace{}, gorm.ErrDuplicatedKey
  81. }
  82. if err != gorm.ErrRecordNotFound {
  83. return jcstypes.UserSpace{}, err
  84. }
  85. }
  86. space.Name = req.Name
  87. space.Credential = req.Credential
  88. space.Features = req.Features
  89. space.Revision += 1
  90. return space, db2.UserSpace().UpdateColumns(tx, space, "Name", "Credential", "Features", "Revision")
  91. })
  92. if err == gorm.ErrDuplicatedKey {
  93. return nil, ecode.New(ecode.DataExists, "user space name already exists")
  94. }
  95. if err != nil {
  96. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  97. }
  98. // 通知元数据缓存无效
  99. svc.UserSpaceMeta.Drop([]jcstypes.UserSpaceID{req.UserSpaceID})
  100. // 通知存储服务组件池停止组件。TODO 对于在Hub上运行的组件,需要一个机制去定时清理
  101. svc.StgPool.Drop(stgglb.UserID, space.UserSpaceID)
  102. // TODO 考虑加锁再进行操作
  103. return &cliapi.UserSpaceUpdateResp{UserSpace: space}, nil
  104. }
  105. func (svc *UserSpaceService) Delete(req cliapi.UserSpaceDelete) (*cliapi.UserSpaceDeleteResp, *ecode.CodeError) {
  106. db2 := svc.DB
  107. err := db2.DoTx(func(tx db.SQLContext) error {
  108. err := db2.UserSpace().DeleteByID(tx, req.UserSpaceID)
  109. if err != nil {
  110. return err
  111. }
  112. err = db2.ObjectBlock().DeleteByUserSpaceID(tx, req.UserSpaceID)
  113. if err != nil {
  114. return err
  115. }
  116. err = db2.PinnedObject().DeleteByUserSpaceID(tx, req.UserSpaceID)
  117. if err != nil {
  118. return err
  119. }
  120. err = db2.ObjectAccessStat().DeleteByUserSpaceID(tx, req.UserSpaceID)
  121. if err != nil {
  122. return err
  123. }
  124. err = db2.PackageAccessStat().DeleteByUserSpaceID(tx, req.UserSpaceID)
  125. if err != nil {
  126. return err
  127. }
  128. return nil
  129. })
  130. if err != nil {
  131. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  132. }
  133. // 通知元数据缓存无效
  134. svc.UserSpaceMeta.Drop([]jcstypes.UserSpaceID{req.UserSpaceID})
  135. // 通知存储服务组件池停止组件。TODO 对于在Hub上运行的组件,需要一个机制去定时清理
  136. svc.StgPool.Drop(stgglb.UserID, req.UserSpaceID)
  137. // TODO 考虑加锁再进行操作,并且增加机制打断已经在进行的操作。
  138. return &cliapi.UserSpaceDeleteResp{}, nil
  139. }
  140. func (svc *UserSpaceService) Test(req cliapi.UserSpaceTest) (*cliapi.UserSpaceTestResp, *ecode.CodeError) {
  141. detail := jcstypes.UserSpaceDetail{
  142. UserID: stgglb.UserID,
  143. UserSpace: jcstypes.UserSpace{
  144. Name: "test",
  145. Storage: req.Storage,
  146. Credential: req.Credential,
  147. WorkingDir: jcstypes.PathFromJcsPathString(req.WorikingDir),
  148. },
  149. }
  150. blder := factory.GetBuilder(&detail)
  151. baseStore, err := blder.CreateBaseStore(false)
  152. if err != nil {
  153. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  154. }
  155. err = baseStore.Test()
  156. if err != nil {
  157. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  158. }
  159. return &cliapi.UserSpaceTestResp{}, nil
  160. }
  161. func (svc *UserSpaceService) DownloadPackage(packageID jcstypes.PackageID, userspaceID jcstypes.UserSpaceID, rootPath string) error {
  162. destSpace := svc.UserSpaceMeta.Get(userspaceID)
  163. if destSpace == nil {
  164. return fmt.Errorf("userspace not found: %d", userspaceID)
  165. }
  166. details, err := db.DoTx11(svc.DB, svc.DB.Object().GetPackageObjectDetails, packageID)
  167. if err != nil {
  168. return err
  169. }
  170. mutex, err := reqbuilder.NewBuilder().
  171. UserSpace().Buzy(userspaceID).
  172. MutexLock(svc.PubLock)
  173. if err != nil {
  174. return fmt.Errorf("acquire locks failed, err: %w", err)
  175. }
  176. defer mutex.Unlock()
  177. rootJPath := jcstypes.PathFromJcsPathString(rootPath)
  178. dIndex := 0
  179. var pinned []jcstypes.PinnedObject
  180. for dIndex < len(details) {
  181. plans := exec.NewPlanBuilder()
  182. for i := 0; i < 10 && dIndex < len(details); i++ {
  183. strg, err := svc.StrategySelector.Select(strategy.Request{
  184. Detail: details[dIndex],
  185. DestLocation: destSpace.UserSpace.Storage.GetLocation(),
  186. })
  187. if err != nil {
  188. return fmt.Errorf("select download strategy: %w", err)
  189. }
  190. shouldAtClient := svc.SpeedStats.ShouldAtClient(details[dIndex].Object.Size)
  191. ft := ioswitch2.NewFromTo()
  192. switch strg := strg.(type) {
  193. case *strategy.DirectStrategy:
  194. if shouldAtClient && strg.UserSpace.RecommendHub != nil {
  195. newSpace := strg.UserSpace
  196. newSpace.RecommendHub = nil
  197. ft.AddFrom(ioswitch2.NewFromShardstore(strg.Detail.Object.FileHash, newSpace, ioswitch2.RawStream()))
  198. } else {
  199. ft.AddFrom(ioswitch2.NewFromShardstore(strg.Detail.Object.FileHash, strg.UserSpace, ioswitch2.RawStream()))
  200. }
  201. case *strategy.ECReconstructStrategy:
  202. for i, b := range strg.Blocks {
  203. if shouldAtClient && strg.UserSpaces[i].RecommendHub != nil {
  204. newSpace := strg.UserSpaces[i]
  205. newSpace.RecommendHub = nil
  206. ft.AddFrom(ioswitch2.NewFromShardstore(b.FileHash, newSpace, ioswitch2.RawStream()))
  207. } else {
  208. ft.AddFrom(ioswitch2.NewFromShardstore(b.FileHash, strg.UserSpaces[i], ioswitch2.RawStream()))
  209. }
  210. ft.ECParam = &strg.Redundancy
  211. }
  212. default:
  213. return fmt.Errorf("unsupported download strategy: %T", strg)
  214. }
  215. objPath := jcstypes.PathFromJcsPathString(details[dIndex].Object.Path)
  216. dstPath := rootJPath.ConcatNew(objPath)
  217. newDstSpace := *destSpace
  218. if shouldAtClient {
  219. newDstSpace.RecommendHub = nil
  220. }
  221. ft.AddTo(ioswitch2.NewToBaseStore(newDstSpace, dstPath))
  222. // 顺便保存到同存储服务的分片存储中
  223. if destSpace.UserSpace.ShardStore != nil {
  224. ft.AddTo(ioswitch2.NewToShardStore(newDstSpace, ioswitch2.RawStream(), ""))
  225. pinned = append(pinned, jcstypes.PinnedObject{
  226. ObjectID: details[dIndex].Object.ObjectID,
  227. UserSpaceID: destSpace.UserSpace.UserSpaceID,
  228. CreateTime: time.Now(),
  229. })
  230. }
  231. err = parser.Parse(ft, plans)
  232. if err != nil {
  233. return fmt.Errorf("parse plan: %w", err)
  234. }
  235. dIndex++
  236. }
  237. // 记录访问统计
  238. for _, obj := range details {
  239. svc.AccessStat.AddAccessCounter(obj.Object.ObjectID, packageID, userspaceID, 1)
  240. }
  241. exeCtx := exec.NewExecContext()
  242. exec.SetValueByType(exeCtx, svc.StgPool)
  243. drv := plans.Execute(exeCtx)
  244. ret, err := drv.Wait(context.Background())
  245. if err != nil {
  246. return err
  247. }
  248. for _, v := range ret.GetArray(ops2.BaseReadStatsStoreKey) {
  249. v2 := v.(*ops2.BaseReadStatsValue)
  250. svc.SpeedStats.Record(v2.Size, v2.Time, v2.Location.IsDriver)
  251. }
  252. err = svc.DB.DoTx(func(tx db.SQLContext) error {
  253. objIDs := make([]jcstypes.ObjectID, len(pinned))
  254. for i, obj := range pinned {
  255. objIDs[i] = obj.ObjectID
  256. }
  257. avaiIDs, err := svc.DB.Object().BatchTestObjectID(tx, objIDs)
  258. if err != nil {
  259. return err
  260. }
  261. pinned = lo.Filter(pinned, func(p jcstypes.PinnedObject, idx int) bool { return avaiIDs[p.ObjectID] })
  262. return svc.DB.PinnedObject().BatchTryCreate(svc.DB.DefCtx(), pinned)
  263. })
  264. if err != nil {
  265. logger.Warnf("create pinned objects: %v", err)
  266. }
  267. }
  268. return nil
  269. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。