storage.pool.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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. "github.com/tidwall/pretty"
  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. sendErrorResponse(w, "Permission Denied")
  30. },
  31. })
  32. adminRouter.HandleFunc("/system/storage/pool/list", HandleListStoragePools)
  33. adminRouter.HandleFunc("/system/storage/pool/listraw", HandleListStoragePoolsConfig)
  34. adminRouter.HandleFunc("/system/storage/pool/newHandler", HandleStorageNewFsHandler)
  35. adminRouter.HandleFunc("/system/storage/pool/removeHandler", HandleStoragePoolRemove)
  36. adminRouter.HandleFunc("/system/storage/pool/reload", HandleStoragePoolReload)
  37. adminRouter.HandleFunc("/system/storage/pool/toggle", HandleFSHToggle)
  38. adminRouter.HandleFunc("/system/storage/pool/edit", HandleFSHEdit)
  39. }
  40. //Handle editing of a given File System Handler
  41. func HandleFSHEdit(w http.ResponseWriter, r *http.Request) {
  42. opr, _ := mv(r, "opr", false)
  43. uuid, err := mv(r, "uuid", false)
  44. if err != nil {
  45. sendErrorResponse(w, "Invalid UUID")
  46. return
  47. }
  48. group, err := mv(r, "group", false)
  49. if err != nil {
  50. sendErrorResponse(w, "Invalid group given")
  51. return
  52. }
  53. if opr == "get" {
  54. //Load
  55. fshOption, err := getFSHConfigFromGroupAndUUID(group, uuid)
  56. if err != nil {
  57. sendErrorResponse(w, err.Error())
  58. return
  59. }
  60. //Hide the password info
  61. fshOption.Username = ""
  62. fshOption.Password = ""
  63. //Return as JSON
  64. js, _ := json.Marshal(fshOption)
  65. sendJSONResponse(w, string(js))
  66. return
  67. } else if opr == "set" {
  68. //Set
  69. newFsOption := buildOptionFromRequestForm(r)
  70. log.Println(newFsOption)
  71. //Read and remove the original settings from the config file
  72. err := setFSHConfigByGroupAndId(group, uuid, newFsOption)
  73. if err != nil {
  74. errmsg, _ := json.Marshal(err.Error())
  75. http.Redirect(w, r, "../../../SystemAO/storage/updateError.html#"+string(errmsg), 307)
  76. } else {
  77. http.Redirect(w, r, "../../../SystemAO/storage/updateComplete.html#"+group, 307)
  78. }
  79. } else {
  80. //Unknown
  81. sendErrorResponse(w, "Unknown opr given")
  82. return
  83. }
  84. }
  85. //Get the FSH configuration for the given group and uuid
  86. func getFSHConfigFromGroupAndUUID(group string, uuid string) (*fs.FileSystemOption, error) {
  87. //Spot the desired config file
  88. targerFile := ""
  89. if group == "system" {
  90. targerFile = "./system/storage.json"
  91. } else {
  92. targerFile = "./system/storage/" + group + ".json"
  93. }
  94. //Check if file exists.
  95. if !fileExists(targerFile) {
  96. log.Println("Config file not found: ", targerFile)
  97. return nil, errors.New("Configuration file not found")
  98. }
  99. //Load and parse the file
  100. configContent, err := ioutil.ReadFile(targerFile)
  101. if err != nil {
  102. return nil, err
  103. }
  104. loadedConfig := []fs.FileSystemOption{}
  105. err = json.Unmarshal(configContent, &loadedConfig)
  106. if err != nil {
  107. log.Println("Request to parse config error: "+err.Error(), targerFile)
  108. return nil, err
  109. }
  110. //Look for the target fsh uuid
  111. for _, thisFshConfig := range loadedConfig {
  112. if thisFshConfig.Uuid == uuid {
  113. return &thisFshConfig, nil
  114. }
  115. }
  116. return nil, errors.New("No FSH config found with the uuid")
  117. }
  118. func setFSHConfigByGroupAndId(group string, uuid string, options fs.FileSystemOption) error {
  119. //Spot the desired config file
  120. targerFile := ""
  121. if group == "system" {
  122. targerFile = "./system/storage.json"
  123. } else {
  124. targerFile = "./system/storage/" + group + ".json"
  125. }
  126. //Check if file exists.
  127. if !fileExists(targerFile) {
  128. log.Println("Config file not found: ", targerFile)
  129. return errors.New("Configuration file not found")
  130. }
  131. //Load and parse the file
  132. configContent, err := ioutil.ReadFile(targerFile)
  133. if err != nil {
  134. return err
  135. }
  136. loadedConfig := []fs.FileSystemOption{}
  137. err = json.Unmarshal(configContent, &loadedConfig)
  138. if err != nil {
  139. log.Println("Request to parse config error: "+err.Error(), targerFile)
  140. return err
  141. }
  142. //Filter the old fs handler option with given uuid
  143. newConfig := []fs.FileSystemOption{}
  144. for _, fso := range loadedConfig {
  145. if fso.Uuid != uuid {
  146. newConfig = append(newConfig, fso)
  147. }
  148. }
  149. //Append the new fso to config
  150. newConfig = append(newConfig, options)
  151. //Write config back to file
  152. js, _ := json.Marshal(newConfig)
  153. //Beautify the JSON output
  154. js = []byte(pretty.Pretty(js))
  155. return ioutil.WriteFile(targerFile, js, 0755)
  156. }
  157. //Handle Storage Pool toggle on-off
  158. func HandleFSHToggle(w http.ResponseWriter, r *http.Request) {
  159. fsh, _ := mv(r, "fsh", true)
  160. if fsh == "" {
  161. sendErrorResponse(w, "Invalid File System Handler ID")
  162. return
  163. }
  164. group, _ := mv(r, "group", true)
  165. if group == "" {
  166. sendErrorResponse(w, "Invalid group ID")
  167. return
  168. }
  169. //Check if group exists
  170. if !permissionHandler.GroupExists(group) {
  171. sendErrorResponse(w, "Group not exists")
  172. return
  173. }
  174. //Not allow to modify system reserved fsh
  175. if fsh == "user" || fsh == "tmp" {
  176. sendErrorResponse(w, "Cannot toggle system reserved File System Handler")
  177. return
  178. }
  179. //Check if fsh exists
  180. targetpg := permissionHandler.GetPermissionGroupByName(group)
  181. storagePool := targetpg.StoragePool
  182. var targetFSH *fs.FileSystemHandler
  183. for _, thisFsh := range storagePool.Storages {
  184. if thisFsh.UUID == fsh {
  185. targetFSH = thisFsh
  186. }
  187. }
  188. //Target File System Handler not found
  189. if targetFSH == nil {
  190. sendErrorResponse(w, "Target File System Handler not found, given: "+fsh)
  191. return
  192. }
  193. if targetFSH.Closed == true {
  194. //Reopen the fsh database and set this to false
  195. aofsPath := filepath.ToSlash(filepath.Clean(targetFSH.Path)) + "/aofs.db"
  196. conn, err := database.NewDatabase(aofsPath, false)
  197. if err != nil {
  198. sendErrorResponse(w, "Filesystme database startup failed")
  199. return
  200. }
  201. targetFSH.FilesystemDatabase = conn
  202. targetFSH.Closed = false
  203. } else {
  204. //Close the fsh database and set this to true
  205. targetFSH.FilesystemDatabase.Close()
  206. targetFSH.Closed = true
  207. }
  208. //Give it some time to finish unloading
  209. time.Sleep(1 * time.Second)
  210. //Return ok
  211. sendOK(w)
  212. }
  213. //Handle reload of storage pool
  214. func HandleStoragePoolReload(w http.ResponseWriter, r *http.Request) {
  215. pool, _ := mv(r, "pool", true)
  216. //Basepool super long string just to prevent any typo
  217. if pool == "1eb201a3-d0f6-6630-5e6d-2f40480115c5" {
  218. //Reload ALL storage pools
  219. //Reload basepool
  220. baseStoragePool.Close()
  221. emptyPool := storage.StoragePool{}
  222. baseStoragePool = &emptyPool
  223. fsHandlers = []*fs.FileSystemHandler{}
  224. //Start BasePool again
  225. err := LoadBaseStoragePool()
  226. if err != nil {
  227. log.Println(err.Error())
  228. } else {
  229. //Update userHandler's basePool
  230. userHandler.UpdateStoragePool(baseStoragePool)
  231. }
  232. //Reload all permission group's pool
  233. for _, pg := range permissionHandler.PermissionGroups {
  234. log.Println("Reloading Storage Pool for: " + pg.Name)
  235. //Pool should be exists. Close it
  236. pg.StoragePool.Close()
  237. //Create an empty pool for this permission group
  238. newEmptyPool := storage.StoragePool{}
  239. pg.StoragePool = &newEmptyPool
  240. //Recreate a new pool for this permission group
  241. //If there is no handler in config, the empty one will be kept
  242. LoadStoragePoolForGroup(pg)
  243. }
  244. } else {
  245. if pool == "system" {
  246. //Reload basepool
  247. baseStoragePool.Close()
  248. emptyPool := storage.StoragePool{}
  249. baseStoragePool = &emptyPool
  250. fsHandlers = []*fs.FileSystemHandler{}
  251. //Start BasePool again
  252. err := LoadBaseStoragePool()
  253. if err != nil {
  254. log.Println(err.Error())
  255. } else {
  256. //Update userHandler's basePool
  257. userHandler.UpdateStoragePool(baseStoragePool)
  258. }
  259. } else {
  260. //Reload the given storage pool
  261. if !permissionHandler.GroupExists(pool) {
  262. sendErrorResponse(w, "Permission Pool owner not exists")
  263. return
  264. }
  265. log.Println("Reloading Storage Pool for: " + pool)
  266. //Pool should be exists. Close it
  267. pg := permissionHandler.GetPermissionGroupByName(pool)
  268. pg.StoragePool.Close()
  269. //Create an empty pool for this permission group
  270. newEmptyPool := storage.StoragePool{}
  271. pg.StoragePool = &newEmptyPool
  272. //Recreate a new pool for this permission group
  273. //If there is no handler in config, the empty one will be kept
  274. LoadStoragePoolForGroup(pg)
  275. }
  276. }
  277. sendOK(w)
  278. }
  279. func HandleStoragePoolRemove(w http.ResponseWriter, r *http.Request) {
  280. groupname, err := mv(r, "group", true)
  281. if err != nil {
  282. sendErrorResponse(w, "group not defined")
  283. return
  284. }
  285. uuid, err := mv(r, "uuid", true)
  286. if err != nil {
  287. sendErrorResponse(w, "File system handler UUID not defined")
  288. return
  289. }
  290. targetConfigFile := "./system/storage.json"
  291. if groupname == "system" {
  292. if uuid == "user" || uuid == "tmp" {
  293. sendErrorResponse(w, "Cannot remove system reserved file system handlers")
  294. return
  295. }
  296. //Ok to continue
  297. } else {
  298. //Check group exists
  299. if !permissionHandler.GroupExists(groupname) {
  300. sendErrorResponse(w, "Group not exists")
  301. return
  302. }
  303. if fileExists("./system/storage/" + groupname + ".json") {
  304. targetConfigFile = "./system/storage/" + groupname + ".json"
  305. } else {
  306. //No config to delete
  307. sendErrorResponse(w, "File system handler not exists")
  308. return
  309. }
  310. }
  311. //Remove it from the json file
  312. //Read and parse from old config
  313. oldConfigs := []fs.FileSystemOption{}
  314. originalConfigFile, _ := ioutil.ReadFile(targetConfigFile)
  315. err = json.Unmarshal(originalConfigFile, &oldConfigs)
  316. if err != nil {
  317. sendErrorResponse(w, "Failed to parse original config file")
  318. return
  319. }
  320. //Generate new confic by filtering
  321. newConfigs := []fs.FileSystemOption{}
  322. for _, config := range oldConfigs {
  323. if config.Uuid != uuid {
  324. newConfigs = append(newConfigs, config)
  325. }
  326. }
  327. //Parse and put it into file
  328. if len(newConfigs) > 0 {
  329. js, _ := json.Marshal(newConfigs)
  330. resultingJson := pretty.Pretty(js)
  331. ioutil.WriteFile(targetConfigFile, resultingJson, 755)
  332. } else {
  333. os.Remove(targetConfigFile)
  334. }
  335. sendOK(w)
  336. }
  337. //Constract a fsoption from form
  338. func buildOptionFromRequestForm(r *http.Request) fs.FileSystemOption {
  339. r.ParseForm()
  340. autoMount := (r.FormValue("automount") == "on")
  341. newFsOption := fs.FileSystemOption{
  342. Name: r.FormValue("name"),
  343. Uuid: r.FormValue("uuid"),
  344. Path: r.FormValue("path"),
  345. Access: r.FormValue("access"),
  346. Hierarchy: r.FormValue("hierarchy"),
  347. Automount: autoMount,
  348. Filesystem: r.FormValue("filesystem"),
  349. Mountdev: r.FormValue("mountdev"),
  350. Mountpt: r.FormValue("mountpt"),
  351. Username: r.FormValue("username"),
  352. Password: r.FormValue("password"),
  353. }
  354. return newFsOption
  355. }
  356. func HandleStorageNewFsHandler(w http.ResponseWriter, r *http.Request) {
  357. newFsOption := buildOptionFromRequestForm(r)
  358. type errorObject struct {
  359. Message string
  360. Source string
  361. }
  362. //Get group from form data
  363. groupName := r.FormValue("group")
  364. //Check if group exists
  365. if !permissionHandler.GroupExists(groupName) && groupName != "system" {
  366. js, _ := json.Marshal(errorObject{
  367. Message: "Group not exists: " + groupName,
  368. Source: "",
  369. })
  370. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  371. }
  372. //Validate the config
  373. err := fs.ValidateOption(&newFsOption)
  374. if err != nil {
  375. //Serve an error page
  376. js, _ := json.Marshal(errorObject{
  377. Message: err.Error(),
  378. Source: groupName,
  379. })
  380. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  381. return
  382. }
  383. //Ok. Append to the record
  384. configFile := "./system/storage.json"
  385. if groupName != "system" {
  386. configFile = "./system/storage/" + groupName + ".json"
  387. }
  388. //If file exists, merge it to
  389. oldConfigs := []fs.FileSystemOption{}
  390. if fileExists(configFile) {
  391. originalConfigFile, _ := ioutil.ReadFile(configFile)
  392. err := json.Unmarshal(originalConfigFile, &oldConfigs)
  393. if err != nil {
  394. log.Println(err)
  395. }
  396. }
  397. oldConfigs = append(oldConfigs, newFsOption)
  398. //Prepare the content to be written
  399. js, _ := json.Marshal(oldConfigs)
  400. resultingJson := pretty.Pretty(js)
  401. ioutil.WriteFile(configFile, resultingJson, 755)
  402. w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
  403. http.Redirect(w, r, "../../../SystemAO/storage/poolEditor.html#"+groupName, 307)
  404. }
  405. func HandleListStoragePoolsConfig(w http.ResponseWriter, r *http.Request) {
  406. target, _ := mv(r, "target", false)
  407. if target == "" {
  408. target = "system"
  409. }
  410. target = strings.ReplaceAll(filepath.ToSlash(target), "/", "")
  411. //List the target storage pool config
  412. targetFile := "./system/storage.json"
  413. if target != "system" {
  414. targetFile = "./system/storage/" + target + ".json"
  415. }
  416. //Read and serve it
  417. configContent, err := ioutil.ReadFile(targetFile)
  418. if err != nil {
  419. sendErrorResponse(w, "Given group does not have a config file.")
  420. return
  421. } else {
  422. sendJSONResponse(w, string(configContent))
  423. }
  424. }
  425. //Return all storage pool mounted to the system, aka base pool + pg pools
  426. func HandleListStoragePools(w http.ResponseWriter, r *http.Request) {
  427. filter, _ := mv(r, "filter", false)
  428. storagePools := []*storage.StoragePool{}
  429. if filter != "" {
  430. if filter == "system" {
  431. storagePools = append(storagePools, baseStoragePool)
  432. } else {
  433. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  434. if pg.Name == filter {
  435. storagePools = append(storagePools, pg.StoragePool)
  436. }
  437. }
  438. }
  439. } else {
  440. //Add the base pool into the list
  441. storagePools = append(storagePools, baseStoragePool)
  442. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  443. storagePools = append(storagePools, pg.StoragePool)
  444. }
  445. }
  446. js, _ := json.Marshal(storagePools)
  447. sendJSONResponse(w, string(js))
  448. }