123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760 |
- package main
- import (
- "encoding/json"
- "errors"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "time"
- "imuslab.com/arozos/mod/database"
- "imuslab.com/arozos/mod/permission"
- "imuslab.com/arozos/mod/storage/bridge"
- "imuslab.com/arozos/mod/utils"
- fs "imuslab.com/arozos/mod/filesystem"
- prout "imuslab.com/arozos/mod/prouter"
- storage "imuslab.com/arozos/mod/storage"
- )
- /*
- Storage Pool Handler
- author: tobychui
- This script handle the storage pool editing of different permission groups
- */
- func StoragePoolEditorInit() {
- adminRouter := prout.NewModuleRouter(prout.RouterOption{
- ModuleName: "System Settings",
- AdminOnly: true,
- UserHandler: userHandler,
- DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
- utils.SendErrorResponse(w, "Permission Denied")
- },
- })
- //Create the required folder structure
- err := os.MkdirAll("./system/storage", 0775)
- if err != nil {
- log.Println("Create storage pool setting folder failed: ")
- log.Fatal(err)
- }
- adminRouter.HandleFunc("/system/storage/pool/list", HandleListStoragePools)
- adminRouter.HandleFunc("/system/storage/pool/listraw", HandleListStoragePoolsConfig)
- //adminRouter.HandleFunc("/system/storage/pool/newHandler", HandleStorageNewFsHandler)
- adminRouter.HandleFunc("/system/storage/pool/removeHandler", HandleStoragePoolRemove)
- adminRouter.HandleFunc("/system/storage/pool/reload", HandleStoragePoolReload)
- adminRouter.HandleFunc("/system/storage/pool/toggle", HandleFSHToggle)
- adminRouter.HandleFunc("/system/storage/pool/edit", HandleFSHEdit)
- adminRouter.HandleFunc("/system/storage/pool/bridge", HandleFSHBridging)
- adminRouter.HandleFunc("/system/storage/pool/checkBridge", HandleFSHBridgeCheck)
- }
- // Handle editing of a given File System Handler
- func HandleFSHEdit(w http.ResponseWriter, r *http.Request) {
- opr, _ := utils.PostPara(r, "opr")
- group, err := utils.PostPara(r, "group")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid group given")
- return
- }
- if opr == "get" {
- uuid, err := utils.PostPara(r, "uuid")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid UUID")
- return
- }
- //Load
- fshOption, err := getFSHConfigFromGroupAndUUID(group, uuid)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- //Hide the password info
- fshOption.Username = ""
- fshOption.Password = ""
- //Return as JSON
- js, _ := json.Marshal(fshOption)
- utils.SendJSONResponse(w, string(js))
- return
- } else if opr == "set" {
- config, err := utils.PostPara(r, "config")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid UUID")
- return
- }
- newFsOption, err := buildOptionFromRequestForm(config)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- //systemWideLogger.PrintAndLog("Storage", newFsOption, nil)
- uuid := newFsOption.Uuid
- //Read and remove the original settings from the config file
- err = setFSHConfigByGroupAndId(group, uuid, newFsOption)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- } else {
- utils.SendOK(w)
- }
- } else if opr == "new" {
- //New handler
- config, err := utils.PostPara(r, "config")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid config")
- return
- }
- newFsOption, err := buildOptionFromRequestForm(config)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- //Check if group exists
- if !permissionHandler.GroupExists(group) && group != "system" {
- utils.SendErrorResponse(w, "Group not exists: "+group)
- return
- }
- //Validate the config is correct
- err = fs.ValidateOption(&newFsOption)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- configFile := "./system/storage.json"
- if group != "system" {
- configFile = "./system/storage/" + group + ".json"
- }
- //Merge the old config file if exists
- oldConfigs := []fs.FileSystemOption{}
- if fs.FileExists(configFile) {
- originalConfigFile, _ := os.ReadFile(configFile)
- err := json.Unmarshal(originalConfigFile, &oldConfigs)
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", err.Error(), err)
- }
- }
- oldConfigs = append(oldConfigs, newFsOption)
- js, _ := json.MarshalIndent(oldConfigs, "", " ")
- err = os.WriteFile(configFile, js, 0775)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- utils.SendOK(w)
- } else {
- //Unknown
- utils.SendErrorResponse(w, "Unknown opr given")
- return
- }
- }
- // Get the FSH configuration for the given group and uuid
- func getFSHConfigFromGroupAndUUID(group string, uuid string) (*fs.FileSystemOption, error) {
- //Spot the desired config file
- targerFile := ""
- if group == "system" {
- targerFile = "./system/storage.json"
- } else {
- targerFile = "./system/storage/" + group + ".json"
- }
- //Check if file exists.
- if !fs.FileExists(targerFile) {
- systemWideLogger.PrintAndLog("Storage", "Config file not found: "+targerFile, nil)
- return nil, errors.New("Configuration file not found")
- }
- if !fs.FileExists(filepath.Dir(targerFile)) {
- os.MkdirAll(filepath.Dir(targerFile), 0775)
- }
- //Load and parse the file
- configContent, err := os.ReadFile(targerFile)
- if err != nil {
- return nil, err
- }
- loadedConfig := []fs.FileSystemOption{}
- err = json.Unmarshal(configContent, &loadedConfig)
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", "Request to parse config error: "+err.Error()+targerFile, err)
- return nil, err
- }
- //Look for the target fsh uuid
- for _, thisFshConfig := range loadedConfig {
- if thisFshConfig.Uuid == uuid {
- return &thisFshConfig, nil
- }
- }
- return nil, errors.New("No FSH config found with the uuid")
- }
- func setFSHConfigByGroupAndId(group string, uuid string, options fs.FileSystemOption) error {
- //Spot the desired config file
- targerFile := ""
- if group == "system" {
- targerFile = "./system/storage.json"
- } else {
- targerFile = "./system/storage/" + group + ".json"
- }
- //Check if file exists.
- if !fs.FileExists(targerFile) {
- systemWideLogger.PrintAndLog("Storage", "Config file not found: "+targerFile, nil)
- return errors.New("Configuration file not found")
- }
- if !fs.FileExists(filepath.Dir(targerFile)) {
- os.MkdirAll(filepath.Dir(targerFile), 0775)
- }
- //Load and parse the file
- configContent, err := os.ReadFile(targerFile)
- if err != nil {
- return err
- }
- loadedConfig := []fs.FileSystemOption{}
- err = json.Unmarshal(configContent, &loadedConfig)
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", "Request to parse config error: "+err.Error()+targerFile, err)
- return err
- }
- //Filter the old fs handler option with given uuid
- newConfig := []fs.FileSystemOption{}
- var overwritingConfig fs.FileSystemOption
- for _, fso := range loadedConfig {
- if fso.Uuid != uuid {
- newConfig = append(newConfig, fso)
- } else {
- overwritingConfig = fso
- }
- }
- //Continue using the old username and password if it is left empty
- if options.Username == "" {
- options.Username = overwritingConfig.Username
- }
- if options.Password == "" {
- options.Password = overwritingConfig.Password
- }
- //Append the new fso to config
- newConfig = append(newConfig, options)
- //Write config back to file
- js, _ := json.MarshalIndent(newConfig, "", " ")
- return os.WriteFile(targerFile, js, 0775)
- }
- // Handle Storage Pool toggle on-off
- func HandleFSHToggle(w http.ResponseWriter, r *http.Request) {
- fsh, _ := utils.PostPara(r, "fsh")
- if fsh == "" {
- utils.SendErrorResponse(w, "Invalid File System Handler ID")
- return
- }
- group, _ := utils.PostPara(r, "group")
- if group == "" {
- utils.SendErrorResponse(w, "Invalid group ID")
- return
- }
- //Check if group exists
- if group != "system" && !permissionHandler.GroupExists(group) {
- utils.SendErrorResponse(w, "Group not exists")
- return
- }
- //Not allow to modify system reserved fsh
- if fsh == "user" || fsh == "tmp" {
- utils.SendErrorResponse(w, "Cannot toggle system reserved File System Handler")
- return
- }
- //Check if fsh exists
- var targetpg *permission.PermissionGroup
- var storagePool *storage.StoragePool
- if group == "system" {
- //System storage pool.
- storagePool = baseStoragePool
- } else {
- targetpg = permissionHandler.GetPermissionGroupByName(group)
- storagePool = targetpg.StoragePool
- }
- var targetFSH *fs.FileSystemHandler
- for _, thisFsh := range storagePool.Storages {
- if thisFsh.UUID == fsh {
- targetFSH = thisFsh
- }
- }
- //Target File System Handler not found
- if targetFSH == nil {
- utils.SendErrorResponse(w, "Target File System Handler not found, given: "+fsh)
- return
- }
- if targetFSH.Closed {
- //Reopen the fsh database and set this to false
- aofsPath := filepath.ToSlash(filepath.Clean(targetFSH.Path)) + "/aofs.db"
- conn, err := database.NewDatabase(aofsPath, false)
- if err == nil {
- targetFSH.FilesystemDatabase = conn
- }
- targetFSH.Closed = false
- } else {
- //Close the fsh database and set this to true
- if targetFSH.FilesystemDatabase != nil {
- targetFSH.FilesystemDatabase.Close()
- }
- targetFSH.Closed = true
- }
- //Give it some time to finish unloading
- time.Sleep(1 * time.Second)
- //Return ok
- utils.SendOK(w)
- }
- // Handle reload of storage pool
- func HandleStoragePoolReload(w http.ResponseWriter, r *http.Request) {
- pool, _ := utils.PostPara(r, "pool")
- //Basepool super long string just to prevent any typo
- if pool == "1eb201a3-d0f6-6630-5e6d-2f40480115c5" {
- //Reload ALL storage pools
- //Reload basepool
- baseStoragePool.Close()
- emptyPool := storage.StoragePool{}
- baseStoragePool = &emptyPool
- //Start BasePool again
- err := LoadBaseStoragePool()
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", err.Error(), err)
- } else {
- //Update userHandler's basePool
- userHandler.UpdateStoragePool(baseStoragePool)
- }
- //Reload all permission group's pool
- for _, pg := range permissionHandler.PermissionGroups {
- systemWideLogger.PrintAndLog("Storage", "Reloading Storage Pool for: "+pg.Name, err)
- //Pool should be exists. Close it
- pg.StoragePool.Close()
- //Create an empty pool for this permission group
- newEmptyPool := storage.StoragePool{}
- pg.StoragePool = &newEmptyPool
- //Recreate a new pool for this permission group
- //If there is no handler in config, the empty one will be kept
- LoadStoragePoolForGroup(pg)
- }
- BridgeStoragePoolInit()
- } else {
- if pool == "system" {
- //Reload basepool
- baseStoragePool.Close()
- emptyPool := storage.StoragePool{}
- baseStoragePool = &emptyPool
- //Start BasePool again
- err := LoadBaseStoragePool()
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", err.Error(), err)
- } else {
- //Update userHandler's basePool
- userHandler.UpdateStoragePool(baseStoragePool)
- }
- BridgeStoragePoolForGroup("system")
- } else {
- //Reload the given storage pool
- if !permissionHandler.GroupExists(pool) {
- utils.SendErrorResponse(w, "Permission Pool owner not exists")
- return
- }
- systemWideLogger.PrintAndLog("Storage", "Reloading Storage Pool for: "+pool, nil)
- //Pool should be exists. Close it
- pg := permissionHandler.GetPermissionGroupByName(pool)
- //Record a list of uuids that reloaded, use for later checking for bridge remount
- reloadedFshUUIDs := []string{}
- for _, fsh := range pg.StoragePool.Storages {
- //Close the fsh if it is not a bridged one
- isBridged, _ := bridgeManager.IsBridgedFSH(fsh.UUID, pg.Name)
- if !isBridged && !fsh.Closed {
- fsh.Close()
- reloadedFshUUIDs = append(reloadedFshUUIDs, fsh.UUID)
- }
- }
- //Create an empty pool for this permission group
- newEmptyPool := storage.StoragePool{}
- pg.StoragePool = &newEmptyPool
- //Recreate a new pool for this permission group
- //If there is no handler in config, the empty one will be kept
- LoadStoragePoolForGroup(pg)
- BridgeStoragePoolForGroup(pg.Name)
- //Get all the groups that have bridged the reloaded fshs
- rebridgePendingMap := map[string]bool{}
- for _, fshuuid := range reloadedFshUUIDs {
- pgs := bridgeManager.GetBridgedGroups(fshuuid)
- for _, pg := range pgs {
- rebridgePendingMap[pg] = true
- }
- }
- //Debridge and rebridge all the related storage pools
- for pg, _ := range rebridgePendingMap {
- DebridgeAllFSHandlerFromGroup(pg)
- time.Sleep(100 * time.Millisecond)
- BridgeStoragePoolForGroup(pg)
- }
- }
- }
- utils.SendOK(w)
- }
- func HandleStoragePoolRemove(w http.ResponseWriter, r *http.Request) {
- groupname, err := utils.PostPara(r, "group")
- if err != nil {
- utils.SendErrorResponse(w, "group not defined")
- return
- }
- uuid, err := utils.PostPara(r, "uuid")
- if err != nil {
- utils.SendErrorResponse(w, "File system handler UUID not defined")
- return
- }
- targetConfigFile := "./system/storage.json"
- if groupname == "system" {
- if uuid == "user" || uuid == "tmp" {
- utils.SendErrorResponse(w, "Cannot remove system reserved file system handlers")
- return
- }
- //Ok to continue
- } else {
- //Check group exists
- if !permissionHandler.GroupExists(groupname) {
- utils.SendErrorResponse(w, "Group not exists")
- return
- }
- targetConfigFile = "./system/storage/" + groupname + ".json"
- if !fs.FileExists(targetConfigFile) {
- //No config. Create an empty one
- initConfig := []fs.FileSystemOption{}
- js, _ := json.MarshalIndent(initConfig, "", " ")
- os.WriteFile(targetConfigFile, js, 0775)
- }
- }
- //Check if this handler is bridged handler
- bridged, _ := bridgeManager.IsBridgedFSH(uuid, groupname)
- if bridged {
- //Bridged FSH. Remove it from bridge config
- basePool, err := GetStoragePoolByOwner(groupname)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- err = DebridgeFSHandlerFromGroup(uuid, basePool)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- //Remove it from the config
- bridgeManager.RemoveFromConfig(uuid, groupname)
- utils.SendOK(w)
- return
- } else {
- //Remove it from the json file
- //Read and parse from old config
- oldConfigs := []fs.FileSystemOption{}
- originalConfigFile, _ := os.ReadFile(targetConfigFile)
- err = json.Unmarshal(originalConfigFile, &oldConfigs)
- if err != nil {
- utils.SendErrorResponse(w, "Failed to parse original config file")
- return
- }
- //Generate new confic by filtering
- newConfigs := []fs.FileSystemOption{}
- for _, config := range oldConfigs {
- if config.Uuid != uuid {
- newConfigs = append(newConfigs, config)
- }
- }
- //Parse and put it into file
- if len(newConfigs) > 0 {
- js, _ := json.MarshalIndent(newConfigs, "", " ")
- os.WriteFile(targetConfigFile, js, 0777)
- } else {
- os.Remove(targetConfigFile)
- }
- }
- utils.SendOK(w)
- }
- // Constract a fsoption from form
- func buildOptionFromRequestForm(payload string) (fs.FileSystemOption, error) {
- newFsOption := fs.FileSystemOption{}
- err := json.Unmarshal([]byte(payload), &newFsOption)
- if err != nil {
- return fs.FileSystemOption{}, err
- }
- return newFsOption, nil
- }
- /*
- func HandleStorageNewFsHandler(w http.ResponseWriter, r *http.Request) {
- newFsOption, _ := buildOptionFromRequestForm(r)
- type errorObject struct {
- Message string
- Source string
- }
- //Get group from form data
- groupName := r.FormValue("group")
- //Check if group exists
- if !permissionHandler.GroupExists(groupName) && groupName != "system" {
- js, _ := json.Marshal(errorObject{
- Message: "Group not exists: " + groupName,
- Source: "",
- })
- http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
- }
- //Validate the config
- err := fs.ValidateOption(&newFsOption)
- if err != nil {
- //Serve an error page
- js, _ := json.Marshal(errorObject{
- Message: err.Error(),
- Source: groupName,
- })
- http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
- return
- }
- //Ok. Append to the record
- configFile := "./system/storage.json"
- if groupName != "system" {
- configFile = "./system/storage/" + groupName + ".json"
- }
- //If file exists, merge it to
- oldConfigs := []fs.FileSystemOption{}
- if fs.FileExists(configFile) {
- originalConfigFile, _ := os.ReadFile(configFile)
- err := json.Unmarshal(originalConfigFile, &oldConfigs)
- if err != nil {
- systemWideLogger.PrintAndLog(err,nil)
- }
- }
- oldConfigs = append(oldConfigs, newFsOption)
- //Prepare the content to be written
- js, _ := json.Marshal(oldConfigs)
- resultingJson := pretty.Pretty(js)
- err = os.WriteFile(configFile, resultingJson, 0775)
- if err != nil {
- //Write Error. This could sometime happens on Windows host for unknown reason
- js, _ := json.Marshal(errorObject{
- Message: err.Error(),
- Source: groupName,
- })
- http.Redirect(w, r, "../../../SystemAO/storage/error.html#"+string(js), 307)
- return
- }
- w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
- http.Redirect(w, r, "../../../SystemAO/storage/poolEditor.html#"+groupName, 307)
- }
- */
- func HandleListStoragePoolsConfig(w http.ResponseWriter, r *http.Request) {
- target, _ := utils.GetPara(r, "target")
- if target == "" {
- target = "system"
- }
- target = strings.ReplaceAll(filepath.ToSlash(target), "/", "")
- //List the target storage pool config
- targetFile := "./system/storage.json"
- if target != "system" {
- targetFile = "./system/storage/" + target + ".json"
- }
- if !fs.FileExists(targetFile) {
- //Assume no storage.
- nofsh := []*fs.FileSystemOption{}
- js, _ := json.Marshal(nofsh)
- utils.SendJSONResponse(w, string(js))
- return
- }
- //Read and serve it
- configContent, err := os.ReadFile(targetFile)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- } else {
- utils.SendJSONResponse(w, string(configContent))
- }
- }
- // Return all storage pool mounted to the system, aka base pool + pg pools
- func HandleListStoragePools(w http.ResponseWriter, r *http.Request) {
- filter, _ := utils.GetPara(r, "filter")
- storagePools := []*storage.StoragePool{}
- if filter != "" {
- if filter == "system" {
- storagePools = append(storagePools, baseStoragePool)
- } else {
- for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
- if pg.Name == filter {
- storagePools = append(storagePools, pg.StoragePool)
- }
- }
- }
- } else {
- //Add the base pool into the list
- storagePools = append(storagePools, baseStoragePool)
- for _, pg := range userHandler.GetPermissionHandler().PermissionGroups {
- storagePools = append(storagePools, pg.StoragePool)
- }
- }
- js, _ := json.Marshal(storagePools)
- utils.SendJSONResponse(w, string(js))
- }
- // Handler for bridging two FSH, require admin permission
- func HandleFSHBridging(w http.ResponseWriter, r *http.Request) {
- //Get the target pool and fsh to bridge
- basePool, err := utils.PostPara(r, "base")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid base pool")
- return
- }
- //Add the target FSH into the base pool
- basePoolObject, err := GetStoragePoolByOwner(basePool)
- if err != nil {
- systemWideLogger.PrintAndLog("Storage", "Bridge FSH failed: "+err.Error(), err)
- utils.SendErrorResponse(w, "Storage pool not found")
- return
- }
- targetFSH, err := utils.PostPara(r, "fsh")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid File System Handler given")
- return
- }
- fsh, err := GetFsHandlerByUUID(targetFSH)
- if err != nil {
- utils.SendErrorResponse(w, "Given File System Handler UUID does not exists")
- return
- }
- err = BridgeFSHandlerToGroup(fsh, basePoolObject)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- bridgeConfig := bridge.BridgeConfig{
- FSHUUID: fsh.UUID,
- SPOwner: basePoolObject.Owner,
- }
- //Write changes to file
- err = bridgeManager.AppendToConfig(&bridgeConfig)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- utils.SendOK(w)
- }
- func HandleFSHBridgeCheck(w http.ResponseWriter, r *http.Request) {
- basePool, err := utils.PostPara(r, "base")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid base pool")
- return
- }
- fsh, err := utils.PostPara(r, "fsh")
- if err != nil {
- utils.SendErrorResponse(w, "Invalid fsh UUID")
- return
- }
- isBridged, err := bridgeManager.IsBridgedFSH(fsh, basePool)
- if err != nil {
- utils.SendErrorResponse(w, err.Error())
- return
- }
- js, _ := json.Marshal(isBridged)
- utils.SendJSONResponse(w, string(js))
- }
|