storage.pool.go 17 KB

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