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.

parser.go 16 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. package plans
  2. import (
  3. "fmt"
  4. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  5. "gitlink.org.cn/cloudream/common/utils/lo2"
  6. )
  7. type FromToParser interface {
  8. Parse(ft FromTo, blder *PlanBuilder) error
  9. }
  10. type DefaultParser struct {
  11. EC cdssdk.ECRedundancy
  12. }
  13. type ParseContext struct {
  14. Ft FromTo
  15. Nodes []*Node
  16. }
  17. func (p *DefaultParser) Parse(ft FromTo, blder *PlanBuilder) error {
  18. ctx := ParseContext{Ft: ft}
  19. // 分成两个阶段:
  20. // 1. 基于From和To生成更多指令,初步匹配to的需求
  21. err := p.extend(&ctx, ft, blder)
  22. if err != nil {
  23. return err
  24. }
  25. // 2. 优化上一步生成的指令
  26. // 对于删除指令的优化,需要反复进行,直到没有变化为止。
  27. // 从目前实现上来说不会死循环
  28. for {
  29. opted := false
  30. if p.removeUnusedJoin(&ctx) {
  31. opted = true
  32. }
  33. if p.removeUnusedMultiplyOutput(&ctx) {
  34. opted = true
  35. }
  36. if p.removeUnusedSplit(&ctx) {
  37. opted = true
  38. }
  39. if p.omitSplitJoin(&ctx) {
  40. opted = true
  41. }
  42. if !opted {
  43. break
  44. }
  45. }
  46. // 确定指令执行位置的过程,也需要反复进行,直到没有变化为止。
  47. // 从目前实现上来说不会死循环
  48. for {
  49. opted := false
  50. if p.pinIPFSRead(&ctx) {
  51. opted = true
  52. }
  53. if p.pinJoin(&ctx) {
  54. opted = true
  55. }
  56. if p.pinMultiply(&ctx) {
  57. opted = true
  58. }
  59. if p.pinSplit(&ctx) {
  60. opted = true
  61. }
  62. if !opted {
  63. break
  64. }
  65. }
  66. // 下面这些只需要执行一次,但需要按顺序
  67. p.dropUnused(&ctx)
  68. p.storeIPFSWriteResult(&ctx)
  69. p.generateClone(&ctx)
  70. p.generateSend(&ctx)
  71. return p.buildPlan(&ctx, blder)
  72. }
  73. func (p *DefaultParser) findOutputStream(ctx *ParseContext, dataIndex int) *StreamVar {
  74. for _, op := range ctx.Nodes {
  75. for _, o := range op.OutputStreams {
  76. if o.DataIndex == dataIndex {
  77. return o
  78. }
  79. }
  80. }
  81. return nil
  82. }
  83. func (p *DefaultParser) extend(ctx *ParseContext, ft FromTo, blder *PlanBuilder) error {
  84. for _, f := range ft.Froms {
  85. n, err := p.buildFromNode(&ft, f)
  86. if err != nil {
  87. return err
  88. }
  89. ctx.Nodes = append(ctx.Nodes, n)
  90. // 对于完整文件的From,生成Split指令
  91. if f.GetDataIndex() == -1 {
  92. splitOp := &Node{
  93. Env: nil,
  94. Type: &ChunkedSplitOp{ChunkSize: p.EC.ChunkSize, PaddingZeros: true},
  95. }
  96. splitOp.AddInput(n.OutputStreams[0])
  97. for i := 0; i < p.EC.K; i++ {
  98. splitOp.NewOutput(i)
  99. }
  100. ctx.Nodes = append(ctx.Nodes, splitOp)
  101. }
  102. }
  103. // 如果有K个不同的文件块流,则生成Multiply指令,同时针对其生成的流,生成Join指令
  104. ecInputStrs := make(map[int]*StreamVar)
  105. loop:
  106. for _, o := range ctx.Nodes {
  107. for _, s := range o.OutputStreams {
  108. if s.DataIndex >= 0 && ecInputStrs[s.DataIndex] == nil {
  109. ecInputStrs[s.DataIndex] = s
  110. if len(ecInputStrs) == p.EC.K {
  111. break loop
  112. }
  113. }
  114. }
  115. }
  116. if len(ecInputStrs) == p.EC.K {
  117. mulOp := &Node{
  118. Env: nil,
  119. Type: &MultiplyOp{ChunkSize: p.EC.ChunkSize},
  120. }
  121. for _, s := range ecInputStrs {
  122. mulOp.AddInput(s)
  123. }
  124. for i := 0; i < p.EC.N; i++ {
  125. mulOp.NewOutput(i)
  126. }
  127. ctx.Nodes = append(ctx.Nodes, mulOp)
  128. joinOp := &Node{
  129. Env: nil,
  130. Type: &ChunkedJoinOp{p.EC.ChunkSize},
  131. }
  132. for i := 0; i < p.EC.K; i++ {
  133. // 不可能找不到流
  134. joinOp.AddInput(p.findOutputStream(ctx, i))
  135. }
  136. joinOp.NewOutput(-1)
  137. }
  138. // 为每一个To找到一个输入流
  139. for _, t := range ft.Tos {
  140. n, err := p.buildToNode(&ft, t)
  141. if err != nil {
  142. return err
  143. }
  144. ctx.Nodes = append(ctx.Nodes, n)
  145. str := p.findOutputStream(ctx, t.GetDataIndex())
  146. if str == nil {
  147. return fmt.Errorf("no output stream found for data index %d", t.GetDataIndex())
  148. }
  149. n.AddInput(str)
  150. }
  151. return nil
  152. }
  153. func (p *DefaultParser) buildFromNode(ft *FromTo, f From) (*Node, error) {
  154. switch f := f.(type) {
  155. case *FromNode:
  156. n := &Node{
  157. // TODO2 需要FromTo的Range来设置Option
  158. Type: &IPFSReadType{FileHash: ft.Object.Object.FileHash},
  159. }
  160. n.NewOutput(f.DataIndex)
  161. if f.Node != nil {
  162. n.Env = &AgentEnv{Node: *f.Node}
  163. }
  164. return n, nil
  165. case *FromExecutor:
  166. n := &Node{
  167. Env: &ExecutorEnv{},
  168. Type: &FromExecutorOp{Handle: f.Handle},
  169. }
  170. n.NewOutput(f.DataIndex)
  171. return n, nil
  172. default:
  173. return nil, fmt.Errorf("unsupported from type %T", f)
  174. }
  175. }
  176. func (p *DefaultParser) buildToNode(ft *FromTo, t To) (*Node, error) {
  177. switch t := t.(type) {
  178. case *ToAgent:
  179. return &Node{
  180. Env: &AgentEnv{t.Node},
  181. Type: &IPFSWriteType{FileHashStoreKey: t.FileHashStoreKey},
  182. }, nil
  183. case *ToExecutor:
  184. return &Node{
  185. Env: &ExecutorEnv{},
  186. Type: &ToExecutorOp{Handle: t.Handle},
  187. }, nil
  188. default:
  189. return nil, fmt.Errorf("unsupported to type %T", t)
  190. }
  191. }
  192. // 删除输出流未被使用的Join指令
  193. func (p *DefaultParser) removeUnusedJoin(ctx *ParseContext) bool {
  194. opted := false
  195. for i, op := range ctx.Nodes {
  196. _, ok := op.Type.(*ChunkedJoinOp)
  197. if !ok {
  198. continue
  199. }
  200. if len(op.OutputStreams[0].Toes) > 0 {
  201. continue
  202. }
  203. for _, in := range op.InputStreams {
  204. in.RemoveTo(op)
  205. }
  206. ctx.Nodes[i] = nil
  207. opted = true
  208. }
  209. ctx.Nodes = lo2.RemoveAllDefault(ctx.Nodes)
  210. return opted
  211. }
  212. // 减少未使用的Multiply指令的输出流。如果减少到0,则删除该指令
  213. func (p *DefaultParser) removeUnusedMultiplyOutput(ctx *ParseContext) bool {
  214. opted := false
  215. for i, op := range ctx.Nodes {
  216. _, ok := op.Type.(*MultiplyOp)
  217. if !ok {
  218. continue
  219. }
  220. for i2, out := range op.OutputStreams {
  221. if len(out.Toes) > 0 {
  222. continue
  223. }
  224. op.OutputStreams[i2] = nil
  225. }
  226. op.OutputStreams = lo2.RemoveAllDefault(op.OutputStreams)
  227. if len(op.OutputStreams) == 0 {
  228. for _, in := range op.InputStreams {
  229. in.RemoveTo(op)
  230. }
  231. ctx.Nodes[i] = nil
  232. }
  233. opted = true
  234. }
  235. ctx.Nodes = lo2.RemoveAllDefault(ctx.Nodes)
  236. return opted
  237. }
  238. // 删除未使用的Split指令
  239. func (p *DefaultParser) removeUnusedSplit(ctx *ParseContext) bool {
  240. opted := false
  241. for i, op := range ctx.Nodes {
  242. _, ok := op.Type.(*ChunkedSplitOp)
  243. if !ok {
  244. continue
  245. }
  246. // Split出来的每一个流都没有被使用,才能删除这个指令
  247. isAllUnused := true
  248. for _, out := range op.OutputStreams {
  249. if len(out.Toes) > 0 {
  250. isAllUnused = false
  251. break
  252. }
  253. }
  254. if isAllUnused {
  255. op.InputStreams[0].RemoveTo(op)
  256. ctx.Nodes[i] = nil
  257. opted = true
  258. }
  259. }
  260. ctx.Nodes = lo2.RemoveAllDefault(ctx.Nodes)
  261. return opted
  262. }
  263. // 如果Split的结果被完全用于Join,则省略Split和Join指令
  264. func (p *DefaultParser) omitSplitJoin(ctx *ParseContext) bool {
  265. opted := false
  266. loop:
  267. for iSplit, splitOp := range ctx.Nodes {
  268. // 进行合并操作时会删除多个指令,因此这里存在splitOp == nil的情况
  269. if splitOp == nil {
  270. continue
  271. }
  272. _, ok := splitOp.Type.(*ChunkedSplitOp)
  273. if !ok {
  274. continue
  275. }
  276. // Split指令的每一个输出都有且只有一个目的地
  277. var joinOp *Node
  278. for _, out := range splitOp.OutputStreams {
  279. if len(out.Toes) != 1 {
  280. continue
  281. }
  282. if joinOp == nil {
  283. joinOp = out.Toes[0]
  284. } else if joinOp != out.Toes[0] {
  285. continue loop
  286. }
  287. }
  288. if joinOp == nil {
  289. continue
  290. }
  291. // 且这个目的地要是一个Join指令
  292. _, ok = joinOp.Type.(*ChunkedJoinOp)
  293. if !ok {
  294. continue
  295. }
  296. // 同时这个Join指令的输入也必须全部来自Split指令的输出。
  297. // 由于上面判断了Split指令的输出目的地都相同,所以这里只要判断Join指令的输入数量是否与Split指令的输出数量相同即可
  298. if len(joinOp.InputStreams) != len(splitOp.OutputStreams) {
  299. continue
  300. }
  301. // 所有条件都满足,可以开始省略操作,将Join操作的目的地的输入流替换为Split操作的输入流:
  302. // F->Split->Join->T 变换为:F->T
  303. splitOp.InputStreams[0].RemoveTo(splitOp)
  304. for _, to := range joinOp.OutputStreams[0].Toes {
  305. to.ReplaceInput(joinOp.OutputStreams[0], splitOp.InputStreams[0])
  306. }
  307. // 并删除这两个指令
  308. ctx.Nodes[iSplit] = nil
  309. lo2.Clear(ctx.Nodes, joinOp)
  310. opted = true
  311. }
  312. ctx.Nodes = lo2.RemoveAllDefault(ctx.Nodes)
  313. return opted
  314. }
  315. // 确定Split命令的执行位置
  316. func (p *DefaultParser) pinSplit(ctx *ParseContext) bool {
  317. opted := false
  318. for _, op := range ctx.Nodes {
  319. _, ok := op.Type.(*ChunkedSplitOp)
  320. if !ok {
  321. continue
  322. }
  323. // 如果Split的每一个流的目的地都是同一个,则将Split固定在这个地方执行
  324. var toEnv OpEnv
  325. useToEnv := true
  326. for _, out := range op.OutputStreams {
  327. for _, to := range out.Toes {
  328. // 如果某个流的目的地也不确定,则将其视为与其他流的目的地相同
  329. if to.Env == nil {
  330. continue
  331. }
  332. if toEnv == nil {
  333. toEnv = to.Env
  334. } else if toEnv.Equals(to.Env) {
  335. useToEnv = false
  336. break
  337. }
  338. }
  339. if !useToEnv {
  340. break
  341. }
  342. }
  343. // 所有输出流的目的地都不确定,那么就不能根据输出流去固定
  344. if toEnv == nil {
  345. useToEnv = false
  346. }
  347. if useToEnv {
  348. if op.Env == nil || !op.Env.Equals(toEnv) {
  349. opted = true
  350. }
  351. op.Env = toEnv
  352. continue
  353. }
  354. // 此时查看输入流的始发地是否可以确定,可以的话使用这个位置
  355. fromEnv := op.InputStreams[0].From.Env
  356. if fromEnv != nil {
  357. if op.Env == nil || !op.Env.Equals(fromEnv) {
  358. opted = true
  359. }
  360. op.Env = fromEnv
  361. }
  362. }
  363. return opted
  364. }
  365. // 确定Join命令的执行位置,策略与固定Split类似
  366. func (p *DefaultParser) pinJoin(ctx *ParseContext) bool {
  367. opted := false
  368. for _, op := range ctx.Nodes {
  369. _, ok := op.Type.(*ChunkedJoinOp)
  370. if !ok {
  371. continue
  372. }
  373. // 先查看输出流的目的地是否可以确定,可以的话使用这个位置
  374. var toEnv OpEnv
  375. for _, to := range op.OutputStreams[0].Toes {
  376. if to.Env == nil {
  377. continue
  378. }
  379. if toEnv == nil {
  380. toEnv = to.Env
  381. } else if !toEnv.Equals(to.Env) {
  382. toEnv = nil
  383. break
  384. }
  385. }
  386. if toEnv != nil {
  387. if op.Env == nil || !op.Env.Equals(toEnv) {
  388. opted = true
  389. }
  390. op.Env = toEnv
  391. continue
  392. }
  393. // 否则根据输入流的始发地来固定
  394. var fromEnv OpEnv
  395. for _, in := range op.InputStreams {
  396. if in.From.Env == nil {
  397. continue
  398. }
  399. if fromEnv == nil {
  400. fromEnv = in.From.Env
  401. } else if !fromEnv.Equals(in.From.Env) {
  402. // 输入流的始发地不同,那也必须选一个作为固定位置
  403. break
  404. }
  405. }
  406. // 所有输入流的始发地都不确定,那没办法了
  407. if fromEnv != nil {
  408. if op.Env == nil || !op.Env.Equals(fromEnv) {
  409. opted = true
  410. }
  411. op.Env = fromEnv
  412. continue
  413. }
  414. }
  415. return opted
  416. }
  417. // 确定Multiply命令的执行位置
  418. func (p *DefaultParser) pinMultiply(ctx *ParseContext) bool {
  419. opted := false
  420. for _, op := range ctx.Nodes {
  421. _, ok := op.Type.(*MultiplyOp)
  422. if !ok {
  423. continue
  424. }
  425. var toEnv OpEnv
  426. for _, out := range op.OutputStreams {
  427. for _, to := range out.Toes {
  428. if to.Env == nil {
  429. continue
  430. }
  431. if toEnv == nil {
  432. toEnv = to.Env
  433. } else if !toEnv.Equals(to.Env) {
  434. toEnv = nil
  435. break
  436. }
  437. }
  438. }
  439. if toEnv != nil {
  440. if op.Env == nil || !op.Env.Equals(toEnv) {
  441. opted = true
  442. }
  443. op.Env = toEnv
  444. continue
  445. }
  446. // 否则根据输入流的始发地来固定
  447. var fromEnv OpEnv
  448. for _, in := range op.InputStreams {
  449. if in.From.Env == nil {
  450. continue
  451. }
  452. if fromEnv == nil {
  453. fromEnv = in.From.Env
  454. } else if !fromEnv.Equals(in.From.Env) {
  455. // 输入流的始发地不同,那也必须选一个作为固定位置
  456. break
  457. }
  458. }
  459. // 所有输入流的始发地都不确定,那没办法了
  460. if fromEnv != nil {
  461. if op.Env == nil || !op.Env.Equals(fromEnv) {
  462. opted = true
  463. }
  464. op.Env = fromEnv
  465. continue
  466. }
  467. }
  468. return opted
  469. }
  470. // 确定IPFS读取指令的执行位置
  471. func (p *DefaultParser) pinIPFSRead(ctx *ParseContext) bool {
  472. opted := false
  473. for _, op := range ctx.Nodes {
  474. _, ok := op.Type.(*IPFSReadType)
  475. if !ok {
  476. continue
  477. }
  478. if op.Env != nil {
  479. continue
  480. }
  481. var toEnv OpEnv
  482. for _, to := range op.OutputStreams[0].Toes {
  483. if to.Env == nil {
  484. continue
  485. }
  486. if toEnv == nil {
  487. toEnv = to.Env
  488. } else if !toEnv.Equals(to.Env) {
  489. toEnv = nil
  490. break
  491. }
  492. }
  493. if toEnv != nil {
  494. if op.Env == nil || !op.Env.Equals(toEnv) {
  495. opted = true
  496. }
  497. op.Env = toEnv
  498. }
  499. }
  500. return opted
  501. }
  502. // 对于所有未使用的流,增加Drop指令
  503. func (p *DefaultParser) dropUnused(ctx *ParseContext) {
  504. for _, op := range ctx.Nodes {
  505. for _, out := range op.OutputStreams {
  506. if len(out.Toes) == 0 {
  507. dropOp := &Node{
  508. Env: nil,
  509. Type: &DropOp{},
  510. }
  511. dropOp.AddInput(out)
  512. ctx.Nodes = append(ctx.Nodes, dropOp)
  513. }
  514. }
  515. }
  516. }
  517. // 为IPFS写入指令存储结果
  518. func (p *DefaultParser) storeIPFSWriteResult(ctx *ParseContext) {
  519. for _, op := range ctx.Nodes {
  520. w, ok := op.Type.(*IPFSWriteType)
  521. if !ok {
  522. continue
  523. }
  524. if w.FileHashStoreKey == "" {
  525. continue
  526. }
  527. storeOp := &Node{
  528. Env: &ExecutorEnv{},
  529. Type: &StoreOp{
  530. StoreKey: w.FileHashStoreKey,
  531. },
  532. }
  533. storeOp.AddInputVar(op.OutputValues[0])
  534. ctx.Nodes = append(ctx.Nodes, storeOp)
  535. }
  536. }
  537. // 生成Clone指令
  538. func (p *DefaultParser) generateClone(ctx *ParseContext) {
  539. for _, op := range ctx.Nodes {
  540. for _, out := range op.OutputStreams {
  541. if len(out.Toes) <= 1 {
  542. continue
  543. }
  544. cloneOp := &Node{
  545. Env: op.Env,
  546. Type: &CloneStreamOp{},
  547. }
  548. for _, to := range out.Toes {
  549. to.ReplaceInput(out, cloneOp.NewOutput(out.DataIndex))
  550. }
  551. out.Toes = nil
  552. cloneOp.AddInput(out)
  553. ctx.Nodes = append(ctx.Nodes, cloneOp)
  554. }
  555. for _, out := range op.OutputValues {
  556. if len(out.Toes) <= 1 {
  557. continue
  558. }
  559. cloneOp := &Node{
  560. Env: op.Env,
  561. Type: &CloneVarOp{},
  562. }
  563. for _, to := range out.Toes {
  564. to.ReplaceInputVar(out, cloneOp.NewOutputVar(out.Type))
  565. }
  566. out.Toes = nil
  567. cloneOp.AddInputVar(out)
  568. }
  569. }
  570. }
  571. // 生成Send指令
  572. func (p *DefaultParser) generateSend(ctx *ParseContext) {
  573. for _, op := range ctx.Nodes {
  574. for _, out := range op.OutputStreams {
  575. to := out.Toes[0]
  576. if to.Env.Equals(op.Env) {
  577. continue
  578. }
  579. switch to.Env.(type) {
  580. case *ExecutorEnv:
  581. // 如果是要送到Executor,则只能由Executor主动去拉取
  582. getStrOp := &Node{
  583. Env: &ExecutorEnv{},
  584. Type: &GetStreamOp{},
  585. }
  586. out.Toes = nil
  587. getStrOp.AddInput(out)
  588. to.ReplaceInput(out, getStrOp.NewOutput(out.DataIndex))
  589. ctx.Nodes = append(ctx.Nodes, getStrOp)
  590. case *AgentEnv:
  591. // 如果是要送到Agent,则可以直接发送
  592. sendStrOp := &Node{
  593. Env: op.Env,
  594. Type: &SendStreamOp{},
  595. }
  596. out.Toes = nil
  597. sendStrOp.AddInput(out)
  598. to.ReplaceInput(out, sendStrOp.NewOutput(out.DataIndex))
  599. ctx.Nodes = append(ctx.Nodes, sendStrOp)
  600. }
  601. }
  602. for _, out := range op.OutputValues {
  603. to := out.Toes[0]
  604. if to.Env.Equals(op.Env) {
  605. continue
  606. }
  607. switch to.Env.(type) {
  608. case *ExecutorEnv:
  609. // 如果是要送到Executor,则只能由Executor主动去拉取
  610. getVarOp := &Node{
  611. Env: &ExecutorEnv{},
  612. Type: &GetVarOp{},
  613. }
  614. out.Toes = nil
  615. getVarOp.AddInputVar(out)
  616. to.ReplaceInputVar(out, getVarOp.NewOutputVar(out.Type))
  617. ctx.Nodes = append(ctx.Nodes, getVarOp)
  618. case *AgentEnv:
  619. // 如果是要送到Agent,则可以直接发送
  620. sendVarOp := &Node{
  621. Env: op.Env,
  622. Type: &SendVarOp{},
  623. }
  624. out.Toes = nil
  625. sendVarOp.AddInputVar(out)
  626. to.ReplaceInputVar(out, sendVarOp.NewOutputVar(out.Type))
  627. ctx.Nodes = append(ctx.Nodes, sendVarOp)
  628. }
  629. }
  630. }
  631. }
  632. // 生成Plan
  633. func (p *DefaultParser) buildPlan(ctx *ParseContext, blder *PlanBuilder) error {
  634. for _, op := range ctx.Nodes {
  635. for _, out := range op.OutputStreams {
  636. if out.Var != nil {
  637. continue
  638. }
  639. out.Var = blder.NewStreamVar()
  640. }
  641. for _, in := range op.InputStreams {
  642. if in.Var != nil {
  643. continue
  644. }
  645. in.Var = blder.NewStreamVar()
  646. }
  647. for _, out := range op.OutputValues {
  648. if out.Var != nil {
  649. continue
  650. }
  651. switch out.Type {
  652. case StringValueVar:
  653. out.Var = blder.NewStringVar()
  654. }
  655. }
  656. for _, in := range op.InputValues {
  657. if in.Var != nil {
  658. continue
  659. }
  660. switch in.Type {
  661. case StringValueVar:
  662. in.Var = blder.NewStringVar()
  663. }
  664. }
  665. if err := op.Type.GenerateOp(op, blder); err != nil {
  666. return err
  667. }
  668. }
  669. return nil
  670. }

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