storage.pool.go 17 KB

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