20181222: storageman interfaces, work manager refactor

This commit is contained in:
wanyaoqi
2019-01-22 16:56:47 +08:00
committed by Zexi Li
parent a053e372ef
commit 9b7f468186
16 changed files with 518 additions and 81 deletions
@@ -0,0 +1,13 @@
package storagetypes
// TODO: move models/storages.go storage types to this file
const (
STORAGE_LOCAL = "local"
STORAGE_BAREMETAL = "baremetal"
STORAGE_SHEEPDOG = "sheepdog"
STORAGE_RBD = "rbd"
STORAGE_DOCKER = "docker"
STORAGE_NAS = "nas"
STORAGE_VSAN = "vsan"
STORAGE_NFS = "nfs"
)
-4
View File
@@ -61,7 +61,3 @@ func (w *SWorkManager) Stop() {
func NewWorkManger() *SWorkManager {
return &SWorkManager{}
}
// type ITaskPramas interface {
// To
// }
-1
View File
@@ -59,7 +59,6 @@ func deleteGuest(ctx context.Context, w http.ResponseWriter, r *http.Request) {
if err != nil {
response(ctx, w, err)
} else {
// TODO: CleanGuest
wm.DelayTask(ctx, guest.CleanGuest, migrated)
response(ctx, w, map[string]bool{"delay_clean": true})
}
+22 -2
View File
@@ -23,6 +23,8 @@ import (
"yunion.io/x/pkg/util/seclib"
)
const VNC_PORT_BASE = 5900
type SGuestManager struct {
ServersPath string
Servers map[string]*SKVMGuestInstance
@@ -282,12 +284,10 @@ func (m *SGuestManager) Delete(sid string) (*SKVMGuestInstance, error) {
func (m *SGuestManager) Start(ctx context.Context, sid string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if guest, ok := m.Servers[sid]; ok {
if desc, err := body.Get("desc"); err != nil {
// TODO
guest.SaveDesc(desc)
}
if guest.IsStopped() {
params, _ := body.Get("params")
// TODO
if err := guest.StartGuest(ctx, params); err != nil {
return nil, httperrors.NewBadRequestError("Failed to start server")
} else {
@@ -309,6 +309,26 @@ func (m *SGuestManager) Start(ctx context.Context, sid string, body jsonutils.JS
}
}
func (m *SGuestManager) GetFreeVncPort() int64 {
vncPorts := make(map[int]struct{}, 0)
for _, guest := range m.Servers {
inUsePort := guest.GetVncPort()
if inUsePort > 0 {
vncPorts[inUsePort] = struct{}{}
}
}
var port = 1
for {
if _, ok := vncPorts[port]; !ok && !cloudcommon.IsTcpPortUsed("0.0.0.0", VNC_PORT_BASE+port) &&
!cloudcommon.IsTcpPortUsed("0.0.0.0", MONITOR_PORT_BASE+port) {
break
} else {
port += 1
}
}
return port
}
func initGuestManager(serversPath string) {
if guestManger == nil {
guestManger = NewGuestManager(serversPath)
@@ -18,6 +18,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudcommon"
"yunion.io/x/onecloud/pkg/cloudcommon/httpclients"
"yunion.io/x/onecloud/pkg/cloudcommon/storagetypes"
"yunion.io/x/onecloud/pkg/hostman/guestfs"
"yunion.io/x/onecloud/pkg/hostman/hostinfo"
"yunion.io/x/onecloud/pkg/hostman/monitor"
@@ -169,19 +170,35 @@ func (s *SKVMGuestInstance) DirtyServerRequestStart() {
}
}
// Must called in new goroutine
func (s *SKVMGuestInstance) asyncScriptStart(ctx context.Context, params *jsonutils.JSONDict) {
// TODO
// hostinof.instace().clean_deleted_ports
// Delay Process
func (s *SKVMGuestInstance) asyncScriptStart(ctx context.Context, params interface{}) {
data, ok := params.(*jsonutils.JSONDict)
if !ok {
log.Errorln("asyncScriptStart params error")
return
}
// TODO hostinof.instace().clean_deleted_ports
time.Sleep(100 * time.Millisecond)
var isStarted, tried, err = false, 0, nil
for !isStarted && tried < MAX_TRY {
tried += 1
vncPort := s.manager.GetFreeVncPort()
s.saveVncPort(vncPort)
params.Set("vnc_port", jsonutils.NewInt(vncPort))
s.saveScripts(params)
isStarted, err = s.scriptStart()
if err = s.saveVncPort(vncPort); err != nil {
goto finally
} else {
data.Set("vnc_port", jsonutils.NewInt(vncPort))
}
if err = s.saveScripts(data); err != nil {
goto finally
} else {
isStarted, err = s.scriptStart()
}
finally:
if !isStarted {
log.Errorf("Start VM failed: %s", err)
time.Sleep((1 << (tried - 1)) * time.Seconde)
@@ -189,6 +206,7 @@ func (s *SKVMGuestInstance) asyncScriptStart(ctx context.Context, params *jsonut
log.Infof("VM started ...")
}
}
s.onAsyncScriptStart(ctx, isStarted, err)
}
@@ -205,6 +223,29 @@ func (s *SKVMGuestInstance) onAsyncScriptStart(ctx context.Context, isStarted bo
}
}
func (s *SKVMGuestInstance) saveScripts(data *jsonutils.JSONDict) error {
startScript, err := s.generateStartScript(data)
if err != nil {
return err
}
if err := cloudcommon.FilePutContents(s.GetStartScriptPath, startScript, false); err != nil {
return err
}
stopScript, err := s.generateStopScript(data)
if err != nil {
return err
}
return cloudcommon.FilePutContents(s.GetStopScriptPath, stopScript, false)
}
func (s *SKVMGuestInstance) GetStartScriptPath() string {
return path.Join(s.HomeDir(), "startvm")
}
func (s *SKVMGuestInstance) GetStopScriptPath() string {
return path.Join(s.HomeDir(), "stopvm")
}
func (s *SKVMGuestInstance) ImportServer(pendingDelete bool) {
if s.IsDirtyShotdown() && !pendingDelete {
log.Infof("Server dirty shotdown %s", s.GetName())
@@ -334,6 +375,10 @@ func (s *SKVMGuestInstance) GetVncPort() int {
return -1
}
func (s *SKVMGuestInstance) saveVncPort(port int64) error {
return cloudcommon.FilePutContents(s.GetVncFilePath(), fmt.Sprintf("%d", port), false)
}
func (s *SKVMGuestInstance) SyncStatus() {
if s.IsRunning() {
s.monitor.GetBlockJobs(s.CheckBlockOrRunning)
@@ -360,9 +405,7 @@ func (s *SKVMGuestInstance) SaveDesc(desc jsonutils.JSONObject) error {
}
func (s *SKVMGuestInstance) StartGuest(ctx context.Context, params jsonutils.JSONObject) {
wm.DelayTask(func() {
s.asyncScriptStart(ctx, params)
})
wm.DelayTask(ctx, s.asyncScriptStart, params)
}
func (s *SKVMGuestInstance) DeployFs(deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) {
@@ -372,12 +415,13 @@ func (s *SKVMGuestInstance) DeployFs(deployInfo *guestfs.SDeployInfo) (jsonutils
diskId, _ := disks[0].GetString("disk_id")
disk := hostinfo.GetStorageManager().GetStorageDisk(storageId, diskId)
return disk.DeployGuestFs(s.Desc, deployInfo)
return disk.DeployGuestFs(disk.GetPath, s.Desc, deployInfo)
} else {
return nil, fmt.Errorf("Guest dosen't have disk ??")
}
}
// Delay process
func (s *SKVMGuestInstance) CleanGuest(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
migrated, ok := params.(bool)
if !ok {
@@ -443,8 +487,12 @@ func (s *SKVMGuestInstance) delTmpDisks(ctx context.Context, migrated bool) {
// TODO GetDisksByPath, storagetypes, deleteallsnapshot, delete
d := hostinfo.GetStorageManager().GetDiskByPath(diskPath)
if d != nil && d.GetType == storagetypes.STORAGE_LOCAL && migrated {
d.DeleteAllSnapshot()
d.Delete(ctx)
if err := d.DeleteAllSnapshot(); err != nil {
log.Errorln(err)
}
if err := d.Delete(ctx); err != nil {
log.Errorln(err)
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
package guestman
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/hostman/options"
)
var (
OS_NAME_LINUX = "Linux"
OS_NAME_WINDOWS = "Windows"
OS_NAME_MACOS = "macOS"
OS_NAME_ANDROID = "Android"
OS_NAME_VMWARE = "VMWare"
)
func (s *SKVMGuestInstance) getOsname() string {
if s.Desc.Contains("metadata") {
metadata, _ := s.Desc.Get("metadata")
if metadata.Contains("os_name") {
osname, _ := metadata.GetString("os_name")
return osname
}
}
return OS_NAME_LINUX
}
func (s *SKVMGuestInstance) generateStartScript(data *jsonutils.JSONDict) (string, error) {
osname := s.GetOsname()
if osname == OS_NAME_MACOS {
s.Desc.Set("machine", jsonutils.NewString("q35"))
s.Desc.Set("bios", jsonutils.NewString("UEFI"))
}
vncPort, _ := data.Int("vnc_port")
qemuVersion := options.HostOptions.DefaultQemuVersion
if data.Contains("qemu_version") {
qemuVersion, _ := data.GetString("qemu_version")
}
if qemuVersion == "latest" {
qemuVersion = ""
}
// TODO: isolatedDevsParams := hostinfo.Instance()...
}
+2
View File
@@ -51,6 +51,8 @@ type SHostOptions struct {
PrivatePrefixes []string `help:"IPv4 private prefixes"`
LocalImagePath []string `help:"Local image storage paths"`
SharedStorages []string `help:"Path of shared storages"`
DefaultQemuVersion string `help:"Default qemu version" default:"2.9.1"`
}
var HostOptions SHostOptions
+40 -6
View File
@@ -1,6 +1,7 @@
package storageman
import (
"context"
"fmt"
"path"
@@ -12,8 +13,18 @@ import (
type IDisk interface {
GetId() string
Probe() bool
DeployGuestFs(guestDesc *jsonutils.JSONDict,
deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error)
DeleteAllSnapshot() error
Delete() error
GetPath() string
CreateFromUrl(context.Context, string) error
// CreateFromSnapshot
CreateFromTemplate(context.Context, string, string, int64) error
Resize(context.Context, int64) error
// @params: diskPath, guestDesc, deployInfo
DeployGuestFs(string, *jsonutils.JSONDict, *guestfs.SDeployInfo) (jsonutils.JSONObject, error)
}
type SBaseDisk struct {
@@ -28,14 +39,37 @@ func NewBaseDisk(storage IStorage, id string) *SBaseDisk {
return ret
}
func (d *SBaseDisk) getPath() string {
func (d *SBaseDisk) GetId() string {
return d.Id
}
func (d *SBaseDisk) GetPath() string {
return path.Join(d.Storage.GetPath(), d.Id)
}
func (d *SBaseDisk) DeployGuestFs(
guestDesc *jsonutils.JSONDict,
func (d *SBaseDisk) Probe() error {
return fmt.Errorf("Not implemented")
}
func (d *SBaseDisk) Delete() error {
return fmt.Errorf("Not implemented")
}
func (d *SBaseDisk) CreateFromUrl(context.Context, string) error {
return fmt.Errorf("Not implemented")
}
func (d *SBaseDisk) CreateFromTemplate(context.Context, string, string, int64) error {
return fmt.Errorf("Not implemented")
}
func (d *SBaseDisk) Resize(context.Context, int64) error {
return fmt.Errorf("Not implemented")
}
func (d *SBaseDisk) DeployGuestFs(diskPath string, guestDesc *jsonutils.JSONDict,
deployInfo *guestfs.SDeployInfo) (jsonutils.JSONObject, error) {
var kvmDisk = NewKVMGuestDisk(d.getPath())
var kvmDisk = NewKVMGuestDisk(diskPath)
if kvmDisk.Connect() {
defer kvmDisk.Disconnect()
log.Infof("Kvm Disk Connect Success !!")
+70
View File
@@ -0,0 +1,70 @@
package storageman
import (
"fmt"
"os"
"path"
"yunion.io/x/log"
)
var _ALTER_SUFFIX_ = ".alter"
type SLocalDisk struct {
SBaseDisk
isAlter bool
}
func NewLocalDisk(storage IStorage, id string) *SLocalDisk {
var ret = new(SLocalDisk)
ret.SBaseDisk = *NewBaseDisk(storage, id)
return ret
}
func (d *SLocalDisk) getPath() string {
return path.Join(d.Storage.GetPath(), d.Id)
}
func (d *SLocalDisk) getAlterPath() string {
return path.Join(d.Storage.GetPath(), d.Id, _ALTER_SUFFIX_)
}
func (d *SLocalDisk) GetPath() string {
if d.isAlter {
return d.getAlterPath()
} else {
return d.getPath()
}
}
func (d *SLocalDisk) Probe() error {
if _, err := os.Stat(d.getPath()); !os.IsNotExist(err) {
d.isAlter = false
return nil
} else if _, err := os.Stat(d.getAlterPath()); !os.IsNotExist(err) {
d.isAlter = true
return nil
}
return fmt.Errorf("Disk not found")
}
func (d *SLocalDisk) Delete() error {
dpath := d.GetPath()
log.Infof("Delete guest disk %s", dpath)
if err := d.Storage.DeleteDiskfile(dpath); err != nil {
return err
}
// TODO: PostCreateFromImageFuse umount fuse fs
d.UmountImageFuse()
/* ????????????????
files = os.listdir(self.storage.path)
for f in files:
if f.startswith(self.id):
if not re.match(r'[a-z0-9\-]*\.\d{14}', f):
path = os.path.join(self.storage.path, f)
print 'delete backing-file:', path
self.storage.delete_diskfile(path)
*/
d.Storage.RemoveDisk(d)
return nil
}
+1
View File
@@ -10,6 +10,7 @@ import (
"time"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon/qemutils"
"yunion.io/x/onecloud/pkg/hostman/guestfs"
)
+46
View File
@@ -0,0 +1,46 @@
package storageman
import "context"
type IImageCache interface {
GetPath() string
Load() error
Acquire(context.Context, string, string) error
Release()
Remove() error
GetImageId() string
}
type SImageCacheDesc struct {
name string
format string
id string
chksum string
path string
size int64
}
type SLocalImageCache struct {
imageId string
Manager IImagecacheManager
Size int64
Desc *SImageCacheDesc
consumerCount int
}
func NewLocalImageCache(imageId string, imagecacheManager IImageCacheManger) *SLocalImageCache {
imageCache := new(SLocalImageCache)
imageCache.imageId = imageId
imageCache.Manager = imagecacheManager
return imageCache
}
func (l *SLocalImageCache) Load() error {
// TODO
}
type SRbdImageCache struct {
imageId string
Manager IImagecacheManager
}
@@ -0,0 +1,86 @@
package storageman
import (
"io/ioutil"
"os"
"os/exec"
"sync"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/util/regutils"
)
type IImageCacheManger interface {
// LoadCache() error ???
PrefetchImageCache(ctx, data jsonutils.JSONObject) error
DeleteImageCache(ctx, data jsonutils.JSONObject) error
}
type SBaseImageCacheManager struct {
storagemanager *SStorageManager
storagecacaheId string
cachePath string
cachedImages map[string]IImageCache
mutex *sync.Mutex
}
type SLocalImageCacheManager struct {
SBaseImageCacheManager
limit int
isTemplate bool
}
func NewLocalImageCacheManager(manager *SStorageManager, cachePath string, limit int, isTemplete bool, storagecacheId string) *SLocalImageCacheManager {
imageCacheManager := new(SLocalImageCacheManager)
imageCacheManager.storagemanager = manager
imageCacheManager.storagecacaheId = storagecacheId
imageCacheManager.cachePath = cachePath
imageCacheManager.limit = limit
imageCacheManager.isTemplate = isTemplete
imageCacheManager.cachedImages = make(map[string]IImageCache, 0)
imageCacheManager.mutex = new(sync.Mutex)
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
exec.Command("mkdir", "-p", cachePath).Run()
}
imageCacheManager.loadCache()
}
func (c *SLocalImageCacheManager) loadCache() {
if len(c.cachePath) == 0 {
return
}
c.mutex.Lock()
defer c.mutex.Unlock()
files, _ := ioutil.ReadDir(c.cachePath)
for _, f := range files {
if regutils.MatchUUIDExact(f.Name()) {
c.loadImageCache(f)
}
}
}
func (c *SLocalImageCacheManager) loadImageCache(file string) {
imageCache := NewLocalImageCache(file, c)
if err := imageCache.Load(); err != nil {
c.cachedImages[imageCache.GetImageId()] = imageCache
}
}
// TODO: AgentImageCacheManager
type SAgentImageCacheManager struct {
storagemanager *SStorageManager
}
func NewAgentImageCacheManager(storagemanager *SStorageManager) *SAgentImageCacheManager {
return &SAgentImageCacheManager{storagemanager}
}
type SRbdImageCacheManager struct {
SBaseImageCacheManager
pool, prefix string
storage IStorage
}
func NewRbdImageCacheManager() *SRbdImageCacheManager {
// TODO
}
-25
View File
@@ -1,25 +0,0 @@
package storageman
import "os"
type SLocalDisk struct {
*SBaseDisk
}
func NewLocalDisk(storage IStorage, id string) *SLocalDisk {
var ret = new(SLocalDisk)
ret.SBaseDisk = NewBaseDisk(storage, id)
return ret
}
func (d *SLocalDisk) GetId() string {
return d.Id
}
func (d *SLocalDisk) Probe() bool {
if _, err := os.Stat(d.getPath()); !os.IsNotExist(err) {
return true
}
// TODO alter ??
return false
}
+1
View File
@@ -9,6 +9,7 @@ import (
type IStorage interface {
StorageType() string
GetPath() string
GetFreeSizeMb() int
// Find owner disks first, if not found, call create disk
GetDiskById(diskId string) IDisk
@@ -1,18 +1,20 @@
package storageman
import "yunion.io/x/log"
type SLocalStorage struct {
*SBaseStorage
SBaseStorage
}
func NewLocalStorage(manager *SStorageManager, path string) *SLocalStorage {
var ret = new(SLocalStorage)
ret.SBaseStorage = NewBaseStorage(manager, path)
ret.SBaseStorage = *NewBaseStorage(manager, path)
ret.StartSnapshotRecycle()
return ret
}
func (s *SLocalStorage) StorageType() string {
return "local"
return storagetypes.STORAGE_LOCAL
}
func (s *SLocalStorage) GetDiskById(diskId string) IDisk {
@@ -28,11 +30,13 @@ func (s *SLocalStorage) CreateDisk(diskId string) IDisk {
s.DiskLock.Lock()
defer s.DiskLock.Unlock()
var disk = NewLocalDisk(s, diskId)
if disk.Probe() {
if err := disk.Probe(); err != nil {
s.Disks = append(s.Disks, disk)
return disk
} else {
log.Errorln(err)
return nil
}
return nil
}
func (s *SLocalStorage) StartSnapshotRecycle() {
+121 -24
View File
@@ -1,22 +1,25 @@
package storageman
import (
"yunion.io/x/log"
"fmt"
"os"
"path"
"strings"
"yunion.io/x/onecloud/pkg/hostman/options"
)
/***************************************************************************/
/****************************StorageManager*********************************/
/***************************************************************************/
const MINIMAL_FREE_SPACE = 128
type SStorageManager struct {
storages []IStorage
LocalStorageImagecache IStorageCache
storages []IStorage
AgentStorage IStorage
LocalStorageImagecacheManager IImageCacheManger
AgentStorageImagecacheManager IImageCacheManger
NfsStorageImagecacheMangers []IImageCacheManger
}
func NewStorageManager() *SStorageManager {
func NewStorageManager() (*SStorageManager, error) {
var ret = new(SStorageManager)
ret.storages = make([]IStorage, 0)
var allFull = true
@@ -36,12 +39,97 @@ func NewStorageManager() *SStorageManager {
allFull = False
}
if allFull {
log.Fatalf("Not enough storage space!")
return nil, fmt.Errorf("Not enough storage space!")
}
if err := ret.initLocalStorageImagecache(); err != nil {
return nil, fmt.Errorf("Init Local storage image cache failed: %s", err)
}
ret.initLocalStorageImagecache()
ret.initAgentStorageImagecache()
ret.initAgentStorage()
return ret
if err := ret.initAgentStorage(); err != nil {
return nil, fmt.Errorf("Init agent storage failed: %s", err)
}
return ret, nil
}
func (s *SStorageManager) getLeasedUsedLocalStorage(cacheDir string, limit int) (string, error) {
var maxFree int
var spath string
var maxStorage IStorage
for _, storage := range s.storages {
if _, ok := storage.(*SLocalStorage); ok {
cachePath := path.Join(storage.GetPath(), cacheDir)
if _, err := os.Stat(cachePath); !os.IsNotExist(err) {
spath = cachePath
break
}
free := storage.GetFreeSizeMb()
if maxFree < free {
maxFree = free
maxStorage = storage
}
}
}
if len(spath) == 0 {
if maxFree >= limit*1024 {
spath = path.Join(maxStorage.GetPath(), cacheDir)
} else {
return "", fmt.Errorf("No local storage has free space larger than %dGB", limit)
}
}
return spath, nil
}
func (s *SStorageManager) initLocalStorageImagecache() error {
var cacheDir = "image_cache"
cachePath := options.HostOptions.ImageCachePath
limit := options.HostOptions.ImageCacheLimit
if len(cachePath) == 0 {
var err error
cachePath, err = s.getLeasedUsedLocalStorage(cacheDir, limit)
if err != nil {
return err
}
}
if len(cachePath) == 0 {
s.LocalStorageImagecacheManager = NewLocalImageCacheManager(s, cachePath, limit, true, "")
return nil
} else {
fmt.Errorf("Cannot allocate image cache storage")
}
}
func (s *SStorageManager) initAgentStorageImagecache() {
s.AgentStorageImagecacheManager = NewAgentImageCacheManager(s)
}
func (s *SStorageManager) initAgentStorage() error {
var cacheDir = "agent_tmp"
var spath = options.HostOptions.AgentTempPath
var limit = options.HostOptions.AgentTempLimit
if len(spath) == 0 {
var err error
spath, err = s.getLeasedUsedLocalStorage(cacheDir, limit)
if err != nil {
return err
}
}
if len(spath) != nil {
// TODO: NewAgentStorage
s.AgentStorage = NewAgentStorage(s, spath)
} else {
return fmt.Errorf("Cannot allocate agent storage")
}
}
func (s *SStorageManager) AddNfsStorage(storagecacheId, cachePath string) {
if len(cachePath) == 0 {
return
}
if s.NfsStorageImagecacheMangers == nil {
s.NfsStorageImagecacheMangers = make(map[string]IImageCacheManger, 0)
}
s.NfsStorageImagecacheMangers[storagecacheId] = NewLocalImageCacheManager(s, cachePath,
options.HostOptions.ImageCacheLimit, true, storagecacheId)
}
func (s *SStorageManager) GetStorageDisk(storageId, diskId string) IDisk {
@@ -51,17 +139,26 @@ func (s *SStorageManager) GetStorageDisk(storageId, diskId string) IDisk {
return nil
}
func (s *SStorageManager) initLocalStorageImagecache() {
var cacheDir = "image_cache"
cachePath := options.HostOptions.ImageCachePath
limit := options.HostOptions.ImageCacheLimit
if len(cachePath) == 0 {
cachePath = s.getLeasedUsedLocalStorage(cacheDir, limit)
}
if len(cachePath) == 0 {
// TODO NewLocalImageCacheManager
s.LocalStorageImagecache = NewLocalImageCacheManager(s, cachePath, limit, true)
} else {
log.Fatalf("Cannot allocate image cache storage")
func (s *SStorageManager) GetStorageByPath(sPath string) IStorage {
for _, storage := range s.storages {
if storage.GetPath() == sPath {
return storage
}
}
return nil
}
func (s *SStorageManager) GetDiskByPath(diskPath string) IDisk {
pos := strings.LastIndex(diskPath, "/")
sPath := path[:pos]
diskId := path[pos+1:]
pos = strings.LastIndex(diskId, ".")
if pos > 0 {
diskId = diskId[:pos]
}
storage := s.GetStorageByPath(sPath)
if storage != nil {
return storage.GetDiskById(diskId)
}
return nil
}