storage.pool.go 18 KB

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