storage.pool.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  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. } else {
  260. if pool == "system" {
  261. //Reload basepool
  262. baseStoragePool.Close()
  263. emptyPool := storage.StoragePool{}
  264. baseStoragePool = &emptyPool
  265. fsHandlers = []*fs.FileSystemHandler{}
  266. //Start BasePool again
  267. err := LoadBaseStoragePool()
  268. if err != nil {
  269. log.Println(err.Error())
  270. } else {
  271. //Update userHandler's basePool
  272. userHandler.UpdateStoragePool(baseStoragePool)
  273. }
  274. } else {
  275. //Reload the given storage pool
  276. if !permissionHandler.GroupExists(pool) {
  277. sendErrorResponse(w, "Permission Pool owner not exists")
  278. return
  279. }
  280. log.Println("Reloading Storage Pool for: " + pool)
  281. //Pool should be exists. Close it
  282. pg := permissionHandler.GetPermissionGroupByName(pool)
  283. pg.StoragePool.Close()
  284. //Create an empty pool for this permission group
  285. newEmptyPool := storage.StoragePool{}
  286. pg.StoragePool = &newEmptyPool
  287. //Recreate a new pool for this permission group
  288. //If there is no handler in config, the empty one will be kept
  289. LoadStoragePoolForGroup(pg)
  290. }
  291. }
  292. sendOK(w)
  293. }
  294. func HandleStoragePoolRemove(w http.ResponseWriter, r *http.Request) {
  295. groupname, err := mv(r, "group", true)
  296. if err != nil {
  297. sendErrorResponse(w, "group not defined")
  298. return
  299. }
  300. uuid, err := mv(r, "uuid", true)
  301. if err != nil {
  302. sendErrorResponse(w, "File system handler UUID not defined")
  303. return
  304. }
  305. targetConfigFile := "./system/storage.json"
  306. if groupname == "system" {
  307. if uuid == "user" || uuid == "tmp" {
  308. sendErrorResponse(w, "Cannot remove system reserved file system handlers")
  309. return
  310. }
  311. //Ok to continue
  312. } else {
  313. //Check group exists
  314. if !permissionHandler.GroupExists(groupname) {
  315. sendErrorResponse(w, "Group not exists")
  316. return
  317. }
  318. if fileExists("./system/storage/" + groupname + ".json") {
  319. targetConfigFile = "./system/storage/" + groupname + ".json"
  320. } else {
  321. //No config to delete
  322. sendErrorResponse(w, "File system handler not exists")
  323. return
  324. }
  325. }
  326. //Check if this handler is bridged handler
  327. bridged, _ := bridgeManager.IsBridgedFSH(uuid, groupname)
  328. if bridged {
  329. //Bridged FSH. Remove it from bridge config
  330. basePool, err := GetStoragePoolByOwner(groupname)
  331. if err != nil {
  332. sendErrorResponse(w, err.Error())
  333. return
  334. }
  335. err = DebridgeFSHandlerFromGroup(uuid, basePool)
  336. if err != nil {
  337. sendErrorResponse(w, err.Error())
  338. }
  339. //Remove it from the config
  340. bridgeManager.RemoveFromConfig(uuid, groupname)
  341. sendOK(w)
  342. return
  343. } else {
  344. //Remove it from the json file
  345. //Read and parse from old config
  346. oldConfigs := []fs.FileSystemOption{}
  347. originalConfigFile, _ := ioutil.ReadFile(targetConfigFile)
  348. err = json.Unmarshal(originalConfigFile, &oldConfigs)
  349. if err != nil {
  350. sendErrorResponse(w, "Failed to parse original config file")
  351. return
  352. }
  353. //Generate new confic by filtering
  354. newConfigs := []fs.FileSystemOption{}
  355. for _, config := range oldConfigs {
  356. if config.Uuid != uuid {
  357. newConfigs = append(newConfigs, config)
  358. }
  359. }
  360. //Parse and put it into file
  361. if len(newConfigs) > 0 {
  362. js, _ := json.Marshal(newConfigs)
  363. resultingJson := pretty.Pretty(js)
  364. ioutil.WriteFile(targetConfigFile, resultingJson, 0777)
  365. } else {
  366. os.Remove(targetConfigFile)
  367. }
  368. }
  369. sendOK(w)
  370. }
  371. //Constract a fsoption from form
  372. func buildOptionFromRequestForm(r *http.Request) fs.FileSystemOption {
  373. r.ParseForm()
  374. autoMount := (r.FormValue("automount") == "on")
  375. newFsOption := fs.FileSystemOption{
  376. Name: r.FormValue("name"),
  377. Uuid: r.FormValue("uuid"),
  378. Path: r.FormValue("path"),
  379. Access: r.FormValue("access"),
  380. Hierarchy: r.FormValue("hierarchy"),
  381. Automount: autoMount,
  382. Filesystem: r.FormValue("filesystem"),
  383. Mountdev: r.FormValue("mountdev"),
  384. Mountpt: r.FormValue("mountpt"),
  385. Parentuid: r.FormValue("parentuid"),
  386. BackupMode: r.FormValue("backupmode"),
  387. Username: r.FormValue("username"),
  388. Password: r.FormValue("password"),
  389. }
  390. return newFsOption
  391. }
  392. func HandleStorageNewFsHandler(w http.ResponseWriter, r *http.Request) {
  393. newFsOption := buildOptionFromRequestForm(r)
  394. type errorObject struct {
  395. Message string
  396. Source string
  397. }
  398. //Get group from form data
  399. groupName := r.FormValue("group")
  400. //Check if group exists
  401. if !permissionHandler.GroupExists(groupName) && groupName != "system" {
  402. js, _ := json.Marshal(errorObject{
  403. Message: "Group not exists: " + groupName,
  404. Source: "",
  405. })
  406. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  407. }
  408. //Validate the config
  409. err := fs.ValidateOption(&newFsOption)
  410. if err != nil {
  411. //Serve an error page
  412. js, _ := json.Marshal(errorObject{
  413. Message: err.Error(),
  414. Source: groupName,
  415. })
  416. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  417. return
  418. }
  419. //Ok. Append to the record
  420. configFile := "./system/storage.json"
  421. if groupName != "system" {
  422. configFile = "./system/storage/" + groupName + ".json"
  423. }
  424. //If file exists, merge it to
  425. oldConfigs := []fs.FileSystemOption{}
  426. if fileExists(configFile) {
  427. originalConfigFile, _ := ioutil.ReadFile(configFile)
  428. err := json.Unmarshal(originalConfigFile, &oldConfigs)
  429. if err != nil {
  430. log.Println(err)
  431. }
  432. }
  433. oldConfigs = append(oldConfigs, newFsOption)
  434. //Prepare the content to be written
  435. js, err := json.Marshal(oldConfigs)
  436. resultingJson := pretty.Pretty(js)
  437. err = ioutil.WriteFile(configFile, resultingJson, 0775)
  438. if err != nil {
  439. //Write Error. This could sometime happens on Windows host for unknown reason
  440. js, _ := json.Marshal(errorObject{
  441. Message: err.Error(),
  442. Source: groupName,
  443. })
  444. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  445. return
  446. }
  447. w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
  448. http.Redirect(w, r, "../../../SystemAO/storage/poolEditor.html#"+groupName, 307)
  449. }
  450. func HandleListStoragePoolsConfig(w http.ResponseWriter, r *http.Request) {
  451. target, _ := mv(r, "target", false)
  452. if target == "" {
  453. target = "system"
  454. }
  455. target = strings.ReplaceAll(filepath.ToSlash(target), "/", "")
  456. //List the target storage pool config
  457. targetFile := "./system/storage.json"
  458. if target != "system" {
  459. targetFile = "./system/storage/" + target + ".json"
  460. }
  461. //Read and serve it
  462. configContent, err := ioutil.ReadFile(targetFile)
  463. if err != nil {
  464. sendErrorResponse(w, "Given group does not have a config file.")
  465. return
  466. } else {
  467. sendJSONResponse(w, string(configContent))
  468. }
  469. }
  470. //Return all storage pool mounted to the system, aka base pool + pg pools
  471. func HandleListStoragePools(w http.ResponseWriter, r *http.Request) {
  472. filter, _ := mv(r, "filter", false)
  473. storagePools := []*storage.StoragePool{}
  474. if filter != "" {
  475. if filter == "system" {
  476. storagePools = append(storagePools, baseStoragePool)
  477. } else {
  478. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  479. if pg.Name == filter {
  480. storagePools = append(storagePools, pg.StoragePool)
  481. }
  482. }
  483. }
  484. } else {
  485. //Add the base pool into the list
  486. storagePools = append(storagePools, baseStoragePool)
  487. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  488. storagePools = append(storagePools, pg.StoragePool)
  489. }
  490. }
  491. js, _ := json.Marshal(storagePools)
  492. sendJSONResponse(w, string(js))
  493. }
  494. //Handler for bridging two FSH, require admin permission
  495. func HandleFSHBridging(w http.ResponseWriter, r *http.Request) {
  496. //Get the target pool and fsh to bridge
  497. basePool, err := mv(r, "base", true)
  498. if err != nil {
  499. sendErrorResponse(w, "Invalid base pool")
  500. return
  501. }
  502. //Add the target FSH into the base pool
  503. basePoolObject, err := GetStoragePoolByOwner(basePool)
  504. if err != nil {
  505. sendErrorResponse(w, "Storage pool not found")
  506. return
  507. }
  508. targetFSH, err := mv(r, "fsh", true)
  509. if err != nil {
  510. sendErrorResponse(w, "Invalid fsh given")
  511. return
  512. }
  513. fsh, err := GetFsHandlerByUUID(targetFSH)
  514. if err != nil {
  515. sendErrorResponse(w, "Given FSH UUID does not exists")
  516. return
  517. }
  518. err = BridgeFSHandlerToGroup(fsh, basePoolObject)
  519. if err != nil {
  520. sendErrorResponse(w, err.Error())
  521. return
  522. }
  523. bridgeConfig := bridge.BridgeConfig{
  524. FSHUUID: fsh.UUID,
  525. SPOwner: basePoolObject.Owner,
  526. }
  527. //Write changes to file
  528. err = bridgeManager.AppendToConfig(&bridgeConfig)
  529. if err != nil {
  530. sendErrorResponse(w, err.Error())
  531. return
  532. }
  533. sendOK(w)
  534. }
  535. func HandleFSHBridgeCheck(w http.ResponseWriter, r *http.Request) {
  536. basePool, err := mv(r, "base", true)
  537. if err != nil {
  538. sendErrorResponse(w, "Invalid base pool")
  539. return
  540. }
  541. fsh, err := mv(r, "fsh", true)
  542. if err != nil {
  543. sendErrorResponse(w, "Invalid fsh UUID")
  544. return
  545. }
  546. isBridged, err := bridgeManager.IsBridgedFSH(fsh, basePool)
  547. if err != nil {
  548. sendErrorResponse(w, err.Error())
  549. return
  550. }
  551. js, _ := json.Marshal(isBridged)
  552. sendJSONResponse(w, string(js))
  553. }