storage.pool.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  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.MarshalIndent(newConfig, "", " ")
  153. return ioutil.WriteFile(targerFile, js, 0755)
  154. }
  155. //Handle Storage Pool toggle on-off
  156. func HandleFSHToggle(w http.ResponseWriter, r *http.Request) {
  157. fsh, _ := mv(r, "fsh", true)
  158. if fsh == "" {
  159. sendErrorResponse(w, "Invalid File System Handler ID")
  160. return
  161. }
  162. group, _ := mv(r, "group", true)
  163. if group == "" {
  164. sendErrorResponse(w, "Invalid group ID")
  165. return
  166. }
  167. //Check if group exists
  168. if !permissionHandler.GroupExists(group) {
  169. sendErrorResponse(w, "Group not exists")
  170. return
  171. }
  172. //Not allow to modify system reserved fsh
  173. if fsh == "user" || fsh == "tmp" {
  174. sendErrorResponse(w, "Cannot toggle system reserved File System Handler")
  175. return
  176. }
  177. //Check if fsh exists
  178. targetpg := permissionHandler.GetPermissionGroupByName(group)
  179. storagePool := targetpg.StoragePool
  180. var targetFSH *fs.FileSystemHandler
  181. for _, thisFsh := range storagePool.Storages {
  182. if thisFsh.UUID == fsh {
  183. targetFSH = thisFsh
  184. }
  185. }
  186. //Target File System Handler not found
  187. if targetFSH == nil {
  188. sendErrorResponse(w, "Target File System Handler not found, given: "+fsh)
  189. return
  190. }
  191. if targetFSH.Closed == true {
  192. //Reopen the fsh database and set this to false
  193. aofsPath := filepath.ToSlash(filepath.Clean(targetFSH.Path)) + "/aofs.db"
  194. conn, err := database.NewDatabase(aofsPath, false)
  195. if err != nil {
  196. sendErrorResponse(w, "Filesystme database startup failed")
  197. return
  198. }
  199. targetFSH.FilesystemDatabase = conn
  200. targetFSH.Closed = false
  201. } else {
  202. //Close the fsh database and set this to true
  203. targetFSH.FilesystemDatabase.Close()
  204. targetFSH.Closed = true
  205. }
  206. //Give it some time to finish unloading
  207. time.Sleep(1 * time.Second)
  208. //Return ok
  209. sendOK(w)
  210. }
  211. //Handle reload of storage pool
  212. func HandleStoragePoolReload(w http.ResponseWriter, r *http.Request) {
  213. pool, _ := mv(r, "pool", true)
  214. //Basepool super long string just to prevent any typo
  215. if pool == "1eb201a3-d0f6-6630-5e6d-2f40480115c5" {
  216. //Reload ALL storage pools
  217. //Reload basepool
  218. baseStoragePool.Close()
  219. emptyPool := storage.StoragePool{}
  220. baseStoragePool = &emptyPool
  221. fsHandlers = []*fs.FileSystemHandler{}
  222. //Start BasePool again
  223. err := LoadBaseStoragePool()
  224. if err != nil {
  225. log.Println(err.Error())
  226. } else {
  227. //Update userHandler's basePool
  228. userHandler.UpdateStoragePool(baseStoragePool)
  229. }
  230. //Reload all permission group's pool
  231. for _, pg := range permissionHandler.PermissionGroups {
  232. log.Println("Reloading Storage Pool for: " + pg.Name)
  233. //Pool should be exists. Close it
  234. pg.StoragePool.Close()
  235. //Create an empty pool for this permission group
  236. newEmptyPool := storage.StoragePool{}
  237. pg.StoragePool = &newEmptyPool
  238. //Recreate a new pool for this permission group
  239. //If there is no handler in config, the empty one will be kept
  240. LoadStoragePoolForGroup(pg)
  241. }
  242. } else {
  243. if pool == "system" {
  244. //Reload basepool
  245. baseStoragePool.Close()
  246. emptyPool := storage.StoragePool{}
  247. baseStoragePool = &emptyPool
  248. fsHandlers = []*fs.FileSystemHandler{}
  249. //Start BasePool again
  250. err := LoadBaseStoragePool()
  251. if err != nil {
  252. log.Println(err.Error())
  253. } else {
  254. //Update userHandler's basePool
  255. userHandler.UpdateStoragePool(baseStoragePool)
  256. }
  257. } else {
  258. //Reload the given storage pool
  259. if !permissionHandler.GroupExists(pool) {
  260. sendErrorResponse(w, "Permission Pool owner not exists")
  261. return
  262. }
  263. log.Println("Reloading Storage Pool for: " + pool)
  264. //Pool should be exists. Close it
  265. pg := permissionHandler.GetPermissionGroupByName(pool)
  266. pg.StoragePool.Close()
  267. //Create an empty pool for this permission group
  268. newEmptyPool := storage.StoragePool{}
  269. pg.StoragePool = &newEmptyPool
  270. //Recreate a new pool for this permission group
  271. //If there is no handler in config, the empty one will be kept
  272. LoadStoragePoolForGroup(pg)
  273. }
  274. }
  275. sendOK(w)
  276. }
  277. func HandleStoragePoolRemove(w http.ResponseWriter, r *http.Request) {
  278. groupname, err := mv(r, "group", true)
  279. if err != nil {
  280. sendErrorResponse(w, "group not defined")
  281. return
  282. }
  283. uuid, err := mv(r, "uuid", true)
  284. if err != nil {
  285. sendErrorResponse(w, "File system handler UUID not defined")
  286. return
  287. }
  288. targetConfigFile := "./system/storage.json"
  289. if groupname == "system" {
  290. if uuid == "user" || uuid == "tmp" {
  291. sendErrorResponse(w, "Cannot remove system reserved file system handlers")
  292. return
  293. }
  294. //Ok to continue
  295. } else {
  296. //Check group exists
  297. if !permissionHandler.GroupExists(groupname) {
  298. sendErrorResponse(w, "Group not exists")
  299. return
  300. }
  301. if fileExists("./system/storage/" + groupname + ".json") {
  302. targetConfigFile = "./system/storage/" + groupname + ".json"
  303. } else {
  304. //No config to delete
  305. sendErrorResponse(w, "File system handler not exists")
  306. return
  307. }
  308. }
  309. //Remove it from the json file
  310. //Read and parse from old config
  311. oldConfigs := []fs.FileSystemOption{}
  312. originalConfigFile, _ := ioutil.ReadFile(targetConfigFile)
  313. err = json.Unmarshal(originalConfigFile, &oldConfigs)
  314. if err != nil {
  315. sendErrorResponse(w, "Failed to parse original config file")
  316. return
  317. }
  318. //Generate new confic by filtering
  319. newConfigs := []fs.FileSystemOption{}
  320. for _, config := range oldConfigs {
  321. if config.Uuid != uuid {
  322. newConfigs = append(newConfigs, config)
  323. }
  324. }
  325. //Parse and put it into file
  326. if len(newConfigs) > 0 {
  327. js, _ := json.Marshal(newConfigs)
  328. resultingJson := pretty.Pretty(js)
  329. ioutil.WriteFile(targetConfigFile, resultingJson, 755)
  330. } else {
  331. os.Remove(targetConfigFile)
  332. }
  333. sendOK(w)
  334. }
  335. //Constract a fsoption from form
  336. func buildOptionFromRequestForm(r *http.Request) fs.FileSystemOption {
  337. r.ParseForm()
  338. autoMount := (r.FormValue("automount") == "on")
  339. newFsOption := fs.FileSystemOption{
  340. Name: r.FormValue("name"),
  341. Uuid: r.FormValue("uuid"),
  342. Path: r.FormValue("path"),
  343. Access: r.FormValue("access"),
  344. Hierarchy: r.FormValue("hierarchy"),
  345. Automount: autoMount,
  346. Filesystem: r.FormValue("filesystem"),
  347. Mountdev: r.FormValue("mountdev"),
  348. Mountpt: r.FormValue("mountpt"),
  349. Username: r.FormValue("username"),
  350. Password: r.FormValue("password"),
  351. }
  352. return newFsOption
  353. }
  354. func HandleStorageNewFsHandler(w http.ResponseWriter, r *http.Request) {
  355. newFsOption := buildOptionFromRequestForm(r)
  356. type errorObject struct {
  357. Message string
  358. Source string
  359. }
  360. //Get group from form data
  361. groupName := r.FormValue("group")
  362. //Check if group exists
  363. if !permissionHandler.GroupExists(groupName) && groupName != "system" {
  364. js, _ := json.Marshal(errorObject{
  365. Message: "Group not exists: " + groupName,
  366. Source: "",
  367. })
  368. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  369. }
  370. //Validate the config
  371. err := fs.ValidateOption(&newFsOption)
  372. if err != nil {
  373. //Serve an error page
  374. js, _ := json.Marshal(errorObject{
  375. Message: err.Error(),
  376. Source: groupName,
  377. })
  378. http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
  379. return
  380. }
  381. //Ok. Append to the record
  382. configFile := "./system/storage.json"
  383. if groupName != "system" {
  384. configFile = "./system/storage/" + groupName + ".json"
  385. }
  386. //If file exists, merge it to
  387. oldConfigs := []fs.FileSystemOption{}
  388. if fileExists(configFile) {
  389. originalConfigFile, _ := ioutil.ReadFile(configFile)
  390. err := json.Unmarshal(originalConfigFile, &oldConfigs)
  391. if err != nil {
  392. log.Println(err)
  393. }
  394. }
  395. oldConfigs = append(oldConfigs, newFsOption)
  396. //Prepare the content to be written
  397. js, _ := json.Marshal(oldConfigs)
  398. resultingJson := pretty.Pretty(js)
  399. ioutil.WriteFile(configFile, resultingJson, 775)
  400. w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
  401. http.Redirect(w, r, "../../../SystemAO/storage/poolEditor.html#"+groupName, 307)
  402. }
  403. func HandleListStoragePoolsConfig(w http.ResponseWriter, r *http.Request) {
  404. target, _ := mv(r, "target", false)
  405. if target == "" {
  406. target = "system"
  407. }
  408. target = strings.ReplaceAll(filepath.ToSlash(target), "/", "")
  409. //List the target storage pool config
  410. targetFile := "./system/storage.json"
  411. if target != "system" {
  412. targetFile = "./system/storage/" + target + ".json"
  413. }
  414. //Read and serve it
  415. configContent, err := ioutil.ReadFile(targetFile)
  416. if err != nil {
  417. sendErrorResponse(w, "Given group does not have a config file.")
  418. return
  419. } else {
  420. sendJSONResponse(w, string(configContent))
  421. }
  422. }
  423. //Return all storage pool mounted to the system, aka base pool + pg pools
  424. func HandleListStoragePools(w http.ResponseWriter, r *http.Request) {
  425. filter, _ := mv(r, "filter", false)
  426. storagePools := []*storage.StoragePool{}
  427. if filter != "" {
  428. if filter == "system" {
  429. storagePools = append(storagePools, baseStoragePool)
  430. } else {
  431. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  432. if pg.Name == filter {
  433. storagePools = append(storagePools, pg.StoragePool)
  434. }
  435. }
  436. }
  437. } else {
  438. //Add the base pool into the list
  439. storagePools = append(storagePools, baseStoragePool)
  440. for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
  441. storagePools = append(storagePools, pg.StoragePool)
  442. }
  443. }
  444. js, _ := json.Marshal(storagePools)
  445. sendJSONResponse(w, string(js))
  446. }