storage.pool.go 14 KB

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