storage.pool.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. "imuslab.com/arozos/mod/permission"
  12. "imuslab.com/arozos/mod/storage/bridge"
  13. "imuslab.com/arozos/mod/utils"
  14. fs "imuslab.com/arozos/mod/filesystem"
  15. prout "imuslab.com/arozos/mod/prouter"
  16. storage "imuslab.com/arozos/mod/storage"
  17. )
  18. /*
  19. Storage Pool Handler
  20. author: tobychui
  21. This script handle the storage pool editing of different permission groups
  22. */
  23. func StoragePoolEditorInit() {
  24. adminRouter := prout.NewModuleRouter(prout.RouterOption{
  25. ModuleName: "System Settings",
  26. AdminOnly: true,
  27. UserHandler: userHandler,
  28. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  29. utils.SendErrorResponse(w, "Permission Denied")
  30. },
  31. })
  32. //Create the required folder structure
  33. err := os.MkdirAll("./system/storage", 0775)
  34. if err != nil {
  35. systemWideLogger.PrintAndLog("System", "Create storage pool setting folder failed: ", nil)
  36. systemWideLogger.PrintAndLog("System", fmt.Sprint(err), nil)
  37. os.Exit(1)
  38. }
  39. adminRouter.HandleFunc("/system/storage/pool/list", HandleListStoragePools)
  40. adminRouter.HandleFunc("/system/storage/pool/listraw", HandleListStoragePoolsConfig)
  41. //adminRouter.HandleFunc("/system/storage/pool/newHandler", HandleStorageNewFsHandler)
  42. adminRouter.HandleFunc("/system/storage/pool/removeHandler", HandleStoragePoolRemove)
  43. adminRouter.HandleFunc("/system/storage/pool/reload", HandleStoragePoolReload)
  44. adminRouter.HandleFunc("/system/storage/pool/toggle", HandleFSHToggle)
  45. adminRouter.HandleFunc("/system/storage/pool/edit", HandleFSHEdit)
  46. adminRouter.HandleFunc("/system/storage/pool/bridge", HandleFSHBridging)
  47. adminRouter.HandleFunc("/system/storage/pool/checkBridge", HandleFSHBridgeCheck)
  48. }
  49. // Handle editing of a given File System Handler
  50. func HandleFSHEdit(w http.ResponseWriter, r *http.Request) {
  51. opr, _ := utils.PostPara(r, "opr")
  52. group, err := utils.PostPara(r, "group")
  53. if err != nil {
  54. utils.SendErrorResponse(w, "Invalid group given")
  55. return
  56. }
  57. if opr == "get" {
  58. uuid, err := utils.PostPara(r, "uuid")
  59. if err != nil {
  60. utils.SendErrorResponse(w, "Invalid UUID")
  61. return
  62. }
  63. //Load
  64. fshOption, err := getFSHConfigFromGroupAndUUID(group, uuid)
  65. if err != nil {
  66. utils.SendErrorResponse(w, err.Error())
  67. return
  68. }
  69. //Hide the password info
  70. fshOption.Username = ""
  71. fshOption.Password = ""
  72. //Return as JSON
  73. js, _ := json.Marshal(fshOption)
  74. utils.SendJSONResponse(w, string(js))
  75. return
  76. } else if opr == "set" {
  77. config, err := utils.PostPara(r, "config")
  78. if err != nil {
  79. utils.SendErrorResponse(w, "Invalid UUID")
  80. return
  81. }
  82. newFsOption, err := buildOptionFromRequestForm(config)
  83. if err != nil {
  84. utils.SendErrorResponse(w, err.Error())
  85. return
  86. }
  87. //systemWideLogger.PrintAndLog("Storage", fmt.Sprint(newFsOption), nil)
  88. uuid := newFsOption.Uuid
  89. //Read and remove the original settings from the config file
  90. err = setFSHConfigByGroupAndId(group, uuid, newFsOption)
  91. if err != nil {
  92. utils.SendErrorResponse(w, err.Error())
  93. } else {
  94. utils.SendOK(w)
  95. }
  96. } else if opr == "new" {
  97. //New handler
  98. config, err := utils.PostPara(r, "config")
  99. if err != nil {
  100. utils.SendErrorResponse(w, "Invalid config")
  101. return
  102. }
  103. newFsOption, err := buildOptionFromRequestForm(config)
  104. if err != nil {
  105. utils.SendErrorResponse(w, err.Error())
  106. return
  107. }
  108. //Check if group exists
  109. if !permissionHandler.GroupExists(group) && group != "system" {
  110. utils.SendErrorResponse(w, "Group not exists: "+group)
  111. return
  112. }
  113. //Validate the config is correct
  114. err = fs.ValidateOption(&newFsOption)
  115. if err != nil {
  116. utils.SendErrorResponse(w, err.Error())
  117. return
  118. }
  119. configFile := "./system/storage.json"
  120. if group != "system" {
  121. configFile = "./system/storage/" + group + ".json"
  122. }
  123. //Merge the old config file if exists
  124. oldConfigs := []fs.FileSystemOption{}
  125. if fs.FileExists(configFile) {
  126. originalConfigFile, _ := os.ReadFile(configFile)
  127. err := json.Unmarshal(originalConfigFile, &oldConfigs)
  128. if err != nil {
  129. systemWideLogger.PrintAndLog("Storage", err.Error(), err)
  130. }
  131. }
  132. oldConfigs = append(oldConfigs, newFsOption)
  133. js, _ := json.MarshalIndent(oldConfigs, "", " ")
  134. err = os.WriteFile(configFile, js, 0775)
  135. if err != nil {
  136. utils.SendErrorResponse(w, err.Error())
  137. return
  138. }
  139. utils.SendOK(w)
  140. } else {
  141. //Unknown
  142. utils.SendErrorResponse(w, "Unknown opr given")
  143. return
  144. }
  145. }
  146. // Get the FSH configuration for the given group and uuid
  147. func getFSHConfigFromGroupAndUUID(group string, uuid string) (*fs.FileSystemOption, error) {
  148. //Spot the desired config file
  149. targerFile := ""
  150. if group == "system" {
  151. targerFile = "./system/storage.json"
  152. } else {
  153. targerFile = "./system/storage/" + group + ".json"
  154. }
  155. //Check if file exists.
  156. if !fs.FileExists(targerFile) {
  157. systemWideLogger.PrintAndLog("Storage", "Config file not found: "+targerFile, nil)
  158. return nil, errors.New("Configuration file not found")
  159. }
  160. if !fs.FileExists(filepath.Dir(targerFile)) {
  161. os.MkdirAll(filepath.Dir(targerFile), 0775)
  162. }
  163. //Load and parse the file
  164. configContent, err := os.ReadFile(targerFile)
  165. if err != nil {
  166. return nil, err
  167. }
  168. loadedConfig := []fs.FileSystemOption{}
  169. err = json.Unmarshal(configContent, &loadedConfig)
  170. if err != nil {
  171. systemWideLogger.PrintAndLog("Storage", "Request to parse config error: "+err.Error()+targerFile, err)
  172. return nil, err
  173. }
  174. //Look for the target fsh uuid
  175. for _, thisFshConfig := range loadedConfig {
  176. if thisFshConfig.Uuid == uuid {
  177. return &thisFshConfig, nil
  178. }
  179. }
  180. return nil, errors.New("No FSH config found with the uuid")
  181. }
  182. func setFSHConfigByGroupAndId(group string, uuid string, options fs.FileSystemOption) error {
  183. //Spot the desired config file
  184. targerFile := ""
  185. if group == "system" {
  186. targerFile = "./system/storage.json"
  187. } else {
  188. targerFile = "./system/storage/" + group + ".json"
  189. }
  190. //Check if file exists.
  191. if !fs.FileExists(targerFile) {
  192. systemWideLogger.PrintAndLog("Storage", "Config file not found: "+targerFile, nil)
  193. return errors.New("Configuration file not found")
  194. }
  195. if !fs.FileExists(filepath.Dir(targerFile)) {
  196. os.MkdirAll(filepath.Dir(targerFile), 0775)
  197. }
  198. //Load and parse the file
  199. configContent, err := os.ReadFile(targerFile)
  200. if err != nil {
  201. return err
  202. }
  203. loadedConfig := []fs.FileSystemOption{}
  204. err = json.Unmarshal(configContent, &loadedConfig)
  205. if err != nil {
  206. systemWideLogger.PrintAndLog("Storage", "Request to parse config error: "+err.Error()+targerFile, err)
  207. return err
  208. }
  209. //Filter the old fs handler option with given uuid
  210. newConfig := []fs.FileSystemOption{}
  211. var overwritingConfig fs.FileSystemOption
  212. for _, fso := range loadedConfig {
  213. if fso.Uuid != uuid {
  214. newConfig = append(newConfig, fso)
  215. } else {
  216. overwritingConfig = fso
  217. }
  218. }
  219. //Continue using the old username and password if it is left empty
  220. if options.Username == "" {
  221. options.Username = overwritingConfig.Username
  222. }
  223. if options.Password == "" {
  224. options.Password = overwritingConfig.Password
  225. }
  226. //Append the new fso to config
  227. newConfig = append(newConfig, options)
  228. //Write config back to file
  229. js, _ := json.MarshalIndent(newConfig, "", " ")
  230. return os.WriteFile(targerFile, js, 0775)
  231. }
  232. // Handle Storage Pool toggle on-off
  233. func HandleFSHToggle(w http.ResponseWriter, r *http.Request) {
  234. fsh, _ := utils.PostPara(r, "fsh")
  235. if fsh == "" {
  236. utils.SendErrorResponse(w, "Invalid File System Handler ID")
  237. return
  238. }
  239. group, _ := utils.PostPara(r, "group")
  240. if group == "" {
  241. utils.SendErrorResponse(w, "Invalid group ID")
  242. return
  243. }
  244. //Check if group exists
  245. if group != "system" && !permissionHandler.GroupExists(group) {
  246. utils.SendErrorResponse(w, "Group not exists")
  247. return
  248. }
  249. //Not allow to modify system reserved fsh
  250. if fsh == "user" || fsh == "tmp" {
  251. utils.SendErrorResponse(w, "Cannot toggle system reserved File System Handler")
  252. return
  253. }
  254. //Check if fsh exists
  255. var targetpg *permission.PermissionGroup
  256. var storagePool *storage.StoragePool
  257. if group == "system" {
  258. //System storage pool.
  259. storagePool = baseStoragePool
  260. } else {
  261. targetpg = permissionHandler.GetPermissionGroupByName(group)
  262. storagePool = targetpg.StoragePool
  263. }
  264. var targetFSH *fs.FileSystemHandler
  265. for _, thisFsh := range storagePool.Storages {
  266. if thisFsh.UUID == fsh {
  267. targetFSH = thisFsh
  268. }
  269. }
  270. //Target File System Handler not found
  271. if targetFSH == nil {
  272. utils.SendErrorResponse(w, "Target File System Handler not found, given: "+fsh)
  273. return
  274. }
  275. targetFSH.Closed = !targetFSH.Closed
  276. //Return ok
  277. utils.SendOK(w)
  278. }
  279. // Handle reload of storage pool
  280. func HandleStoragePoolReload(w http.ResponseWriter, r *http.Request) {
  281. pool, _ := utils.PostPara(r, "pool")
  282. //Basepool super long string just to prevent any typo
  283. if pool == "1eb201a3-d0f6-6630-5e6d-2f40480115c5" {
  284. //Reload ALL storage pools
  285. //Reload basepool
  286. baseStoragePool.Close()
  287. emptyPool := storage.StoragePool{}
  288. baseStoragePool = &emptyPool
  289. //Start BasePool again
  290. err := LoadBaseStoragePool()
  291. if err != nil {
  292. systemWideLogger.PrintAndLog("Storage", err.Error(), err)
  293. } else {
  294. //Update userHandler's basePool
  295. userHandler.UpdateStoragePool(baseStoragePool)
  296. }
  297. //Reload all permission group's pool
  298. for _, pg := range permissionHandler.PermissionGroups {
  299. systemWideLogger.PrintAndLog("Storage", "Reloading Storage Pool for: "+pg.Name, err)
  300. //Pool should be exists. Close it
  301. pg.StoragePool.Close()
  302. //Create an empty pool for this permission group
  303. newEmptyPool := storage.StoragePool{}
  304. pg.StoragePool = &newEmptyPool
  305. //Recreate a new pool for this permission group
  306. //If there is no handler in config, the empty one will be kept
  307. LoadStoragePoolForGroup(pg)
  308. }
  309. BridgeStoragePoolInit()
  310. } else {
  311. if pool == "system" {
  312. //Reload basepool
  313. baseStoragePool.Close()
  314. emptyPool := storage.StoragePool{}
  315. baseStoragePool = &emptyPool
  316. //Start BasePool again
  317. err := LoadBaseStoragePool()
  318. if err != nil {
  319. systemWideLogger.PrintAndLog("Storage", err.Error(), err)
  320. } else {
  321. //Update userHandler's basePool
  322. userHandler.UpdateStoragePool(baseStoragePool)
  323. }
  324. BridgeStoragePoolForGroup("system")
  325. } else {
  326. //Reload the given storage pool
  327. if !permissionHandler.GroupExists(pool) {
  328. utils.SendErrorResponse(w, "Permission Pool owner not exists")
  329. return
  330. }
  331. systemWideLogger.PrintAndLog("Storage", "Reloading Storage Pool for: "+pool, nil)
  332. //Pool should be exists. Close it
  333. pg := permissionHandler.GetPermissionGroupByName(pool)
  334. //Record a list of uuids that reloaded, use for later checking for bridge remount
  335. reloadedFshUUIDs := []string{}
  336. for _, fsh := range pg.StoragePool.Storages {
  337. //Close the fsh if it is not a bridged one
  338. isBridged, _ := bridgeManager.IsBridgedFSH(fsh.UUID, pg.Name)
  339. if !isBridged && !fsh.Closed {
  340. fsh.Close()
  341. reloadedFshUUIDs = append(reloadedFshUUIDs, fsh.UUID)
  342. }
  343. }
  344. //Create an empty pool for this permission group
  345. newEmptyPool := storage.StoragePool{}
  346. pg.StoragePool = &newEmptyPool
  347. //Recreate a new pool for this permission group
  348. //If there is no handler in config, the empty one will be kept
  349. LoadStoragePoolForGroup(pg)
  350. BridgeStoragePoolForGroup(pg.Name)
  351. //Get all the groups that have bridged the reloaded fshs
  352. rebridgePendingMap := map[string]bool{}
  353. for _, fshuuid := range reloadedFshUUIDs {
  354. pgs := bridgeManager.GetBridgedGroups(fshuuid)
  355. for _, pg := range pgs {
  356. rebridgePendingMap[pg] = true
  357. }
  358. }
  359. //Debridge and rebridge all the related storage pools
  360. for pg, _ := range rebridgePendingMap {
  361. DebridgeAllFSHandlerFromGroup(pg)
  362. time.Sleep(100 * time.Millisecond)
  363. BridgeStoragePoolForGroup(pg)
  364. }
  365. }
  366. }
  367. utils.SendOK(w)
  368. }
  369. func HandleStoragePoolRemove(w http.ResponseWriter, r *http.Request) {
  370. groupname, err := utils.PostPara(r, "group")
  371. if err != nil {
  372. utils.SendErrorResponse(w, "group not defined")
  373. return
  374. }
  375. uuid, err := utils.PostPara(r, "uuid")
  376. if err != nil {
  377. utils.SendErrorResponse(w, "File system handler UUID not defined")
  378. return
  379. }
  380. targetConfigFile := "./system/storage.json"
  381. if groupname == "system" {
  382. if uuid == "user" || uuid == "tmp" {
  383. utils.SendErrorResponse(w, "Cannot remove system reserved file system handlers")
  384. return
  385. }
  386. //Ok to continue
  387. } else {
  388. //Check group exists
  389. if !permissionHandler.GroupExists(groupname) {
  390. utils.SendErrorResponse(w, "Group not exists")
  391. return
  392. }
  393. targetConfigFile = "./system/storage/" + groupname + ".json"
  394. if !fs.FileExists(targetConfigFile) {
  395. //No config. Create an empty one
  396. initConfig := []fs.FileSystemOption{}
  397. js, _ := json.MarshalIndent(initConfig, "", " ")
  398. os.WriteFile(targetConfigFile, js, 0775)
  399. }
  400. }
  401. //Check if this handler is bridged handler
  402. bridged, _ := bridgeManager.IsBridgedFSH(uuid, groupname)
  403. if bridged {
  404. //Bridged FSH. Remove it from bridge config
  405. basePool, err := GetStoragePoolByOwner(groupname)
  406. if err != nil {
  407. utils.SendErrorResponse(w, err.Error())
  408. return
  409. }
  410. err = DebridgeFSHandlerFromGroup(uuid, basePool)
  411. if err != nil {
  412. utils.SendErrorResponse(w, err.Error())
  413. return
  414. }
  415. //Remove it from the config
  416. bridgeManager.RemoveFromConfig(uuid, groupname)
  417. utils.SendOK(w)
  418. return
  419. } else {
  420. //Remove it from the json file
  421. //Read and parse from old config
  422. oldConfigs := []fs.FileSystemOption{}
  423. originalConfigFile, _ := os.ReadFile(targetConfigFile)
  424. err = json.Unmarshal(originalConfigFile, &oldConfigs)
  425. if err != nil {
  426. utils.SendErrorResponse(w, "Failed to parse original config file")
  427. return
  428. }
  429. //Generate new confic by filtering
  430. newConfigs := []fs.FileSystemOption{}
  431. for _, config := range oldConfigs {
  432. if config.Uuid != uuid {
  433. newConfigs = append(newConfigs, config)
  434. }
  435. }
  436. //Parse and put it into file
  437. if len(newConfigs) > 0 {
  438. js, _ := json.MarshalIndent(newConfigs, "", " ")
  439. os.WriteFile(targetConfigFile, js, 0777)
  440. } else {
  441. os.Remove(targetConfigFile)
  442. }
  443. }
  444. utils.SendOK(w)
  445. }
  446. // Constract a fsoption from form
  447. func buildOptionFromRequestForm(payload string) (fs.FileSystemOption, error) {
  448. newFsOption := fs.FileSystemOption{}
  449. err := json.Unmarshal([]byte(payload), &newFsOption)
  450. //Do data cleaning
  451. newFsOption.Name = strings.TrimSpace(newFsOption.Name)
  452. newFsOption.Uuid = strings.TrimSpace(newFsOption.Uuid)
  453. newFsOption.Path = strings.TrimSpace(newFsOption.Path)
  454. if err != nil {
  455. return fs.FileSystemOption{}, err
  456. }
  457. return newFsOption, nil
  458. }
  459. func HandleListStoragePoolsConfig(w http.ResponseWriter, r *http.Request) {
  460. target, _ := utils.GetPara(r, "target")
  461. if target == "" {
  462. target = "system"
  463. }
  464. target = strings.ReplaceAll(filepath.ToSlash(target), "/", "")
  465. //List the target storage pool config
  466. targetFile := "./system/storage.json"
  467. if target != "system" {
  468. targetFile = "./system/storage/" + target + ".json"
  469. }
  470. if !fs.FileExists(targetFile) {
  471. //Assume no storage.
  472. nofsh := []*fs.FileSystemOption{}
  473. js, _ := json.Marshal(nofsh)
  474. utils.SendJSONResponse(w, string(js))
  475. return
  476. }
  477. //Read and serve it
  478. configContent, err := os.ReadFile(targetFile)
  479. if err != nil {
  480. utils.SendErrorResponse(w, err.Error())
  481. return
  482. } else {
  483. utils.SendJSONResponse(w, string(configContent))
  484. }
  485. }
  486. // Return all storage pool mounted to the system, aka base pool + pg pools
  487. func HandleListStoragePools(w http.ResponseWriter, r *http.Request) {
  488. filter, _ := utils.GetPara(r, "filter")
  489. storagePools := []*storage.StoragePool{}
  490. if filter != "" {
  491. if filter == "system" {
  492. storagePools = append(storagePools, baseStoragePool)
  493. } else {
  494. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  495. if pg.Name == filter {
  496. storagePools = append(storagePools, pg.StoragePool)
  497. }
  498. }
  499. }
  500. } else {
  501. //Add the base pool into the list
  502. storagePools = append(storagePools, baseStoragePool)
  503. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  504. storagePools = append(storagePools, pg.StoragePool)
  505. }
  506. }
  507. js, _ := json.Marshal(storagePools)
  508. utils.SendJSONResponse(w, string(js))
  509. }
  510. // Handler for bridging two FSH, require admin permission
  511. func HandleFSHBridging(w http.ResponseWriter, r *http.Request) {
  512. //Get the target pool and fsh to bridge
  513. basePool, err := utils.PostPara(r, "base")
  514. if err != nil {
  515. utils.SendErrorResponse(w, "Invalid base pool")
  516. return
  517. }
  518. //Add the target FSH into the base pool
  519. basePoolObject, err := GetStoragePoolByOwner(basePool)
  520. if err != nil {
  521. systemWideLogger.PrintAndLog("Storage", "Bridge FSH failed: "+err.Error(), err)
  522. utils.SendErrorResponse(w, "Storage pool not found")
  523. return
  524. }
  525. targetFSH, err := utils.PostPara(r, "fsh")
  526. if err != nil {
  527. utils.SendErrorResponse(w, "Invalid File System Handler given")
  528. return
  529. }
  530. fsh, err := GetFsHandlerByUUID(targetFSH)
  531. if err != nil {
  532. utils.SendErrorResponse(w, "Given File System Handler UUID does not exists")
  533. return
  534. }
  535. err = BridgeFSHandlerToGroup(fsh, basePoolObject)
  536. if err != nil {
  537. utils.SendErrorResponse(w, err.Error())
  538. return
  539. }
  540. bridgeConfig := bridge.BridgeConfig{
  541. FSHUUID: fsh.UUID,
  542. SPOwner: basePoolObject.Owner,
  543. }
  544. //Write changes to file
  545. err = bridgeManager.AppendToConfig(&bridgeConfig)
  546. if err != nil {
  547. utils.SendErrorResponse(w, err.Error())
  548. return
  549. }
  550. utils.SendOK(w)
  551. }
  552. func HandleFSHBridgeCheck(w http.ResponseWriter, r *http.Request) {
  553. basePool, err := utils.PostPara(r, "base")
  554. if err != nil {
  555. utils.SendErrorResponse(w, "Invalid base pool")
  556. return
  557. }
  558. fsh, err := utils.PostPara(r, "fsh")
  559. if err != nil {
  560. utils.SendErrorResponse(w, "Invalid fsh UUID")
  561. return
  562. }
  563. isBridged, err := bridgeManager.IsBridgedFSH(fsh, basePool)
  564. if err != nil {
  565. utils.SendErrorResponse(w, err.Error())
  566. return
  567. }
  568. js, _ := json.Marshal(isBridged)
  569. utils.SendJSONResponse(w, string(js))
  570. }