storage.pool.go 14 KB

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