Files
cloudpods/pkg/hostman/storageman/storage_base.go
T
wanyaoqi 7bf48c992e - add command executor
- host prepare for deploy on container
- procutils adpat to executor
2019-12-04 20:49:39 +08:00

457 lines
13 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storageman
import (
"context"
"fmt"
"io/ioutil"
"path"
"regexp"
"runtime/debug"
"strings"
"sync"
"syscall"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
const (
_RECYCLE_BIN_ = "recycle_bin"
_IMGSAVE_BACKUPS_ = "imgsave_backups"
_SNAPSHOT_PATH_ = "snapshots"
)
var DELETEING_SNAPSHOTS = sync.Map{}
type IStorageFactory interface {
NewStorage(manager *SStorageManager, mountPoint string) IStorage
StorageType() string
}
var (
storagesFactories = make([]IStorageFactory, 0)
)
// for shared storages
func registerStorageFactory(factory IStorageFactory) {
storagesFactories = append(storagesFactories, factory)
}
func NewStorage(manager *SStorageManager, mountPoint, storageType string) IStorage {
for i := range storagesFactories {
if storageType == storagesFactories[i].StorageType() || strings.HasPrefix(mountPoint, storagesFactories[i].StorageType()) {
return storagesFactories[i].NewStorage(manager, mountPoint)
}
}
log.Errorf("no storage driver for %s found", storageType)
return nil
}
type IStorage interface {
GetId() string
GetStorageName() string
GetZoneName() string
SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject)
SyncStorageInfo() (jsonutils.JSONObject, error)
StorageType() string
GetStorageConf() *jsonutils.JSONDict
GetStoragecacheId() string
SetStoragecacheId(storagecacheId string)
SetPath(string)
GetPath() string
GetSnapshotDir() string
GetSnapshotPathByIds(diskId, snapshotId string) string
DeleteSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
GetFreeSizeMb() int
GetCapacity() int
// Find owner disks first, if not found, call create disk
GetDiskById(diskId string) IDisk
CreateDisk(diskId string) IDisk
RemoveDisk(IDisk)
// DeleteDisk(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
// *SDiskCreateByDiskinfo
CreateDiskByDiskinfo(context.Context, interface{}) (jsonutils.JSONObject, error)
SaveToGlance(context.Context, interface{}) (jsonutils.JSONObject, error)
CreateDiskFromSnapshot(context.Context, IDisk, *SDiskCreateByDiskinfo) error
CreateSnapshotFormUrl(ctx context.Context, snapshotUrl, diskId, snapshotPath string) error
DeleteDiskfile(diskPath string) error
GetFuseTmpPath() string
GetFuseMountPath() string
GetImgsaveBackupPath() string
DestinationPrepareMigrate(ctx context.Context, liveMigrate bool, disksUri string, snapshotsUri string,
desc, disksBackingFile, srcSnapshots jsonutils.JSONObject, rebaseDisks bool) error
}
type SBaseStorage struct {
Manager *SStorageManager
StorageId string
Path string
StorageName string
StorageConf *jsonutils.JSONDict
StoragecacheId string
Disks []IDisk
DiskLock *sync.Mutex
}
func NewBaseStorage(manager *SStorageManager, path string) *SBaseStorage {
var ret = new(SBaseStorage)
ret.Disks = make([]IDisk, 0)
ret.DiskLock = new(sync.Mutex)
ret.Manager = manager
ret.Path = path
return ret
}
func (s *SBaseStorage) GetId() string {
return s.StorageId
}
func (s *SBaseStorage) GetStorageName() string {
return s.StorageName
}
func (s *SBaseStorage) GetStoragecacheId() string {
return s.StoragecacheId
}
func (s *SBaseStorage) SetStoragecacheId(storagecacheId string) {
s.StoragecacheId = storagecacheId
}
func (s *SBaseStorage) GetName(generateName func() string) string {
if len(s.StorageName) > 0 {
return s.StorageName
} else {
return generateName()
}
}
func (s *SBaseStorage) GetPath() string {
return s.Path
}
func (s *SBaseStorage) SetPath(p string) {
s.Path = p
}
func (s *SBaseStorage) GetZoneName() string {
return s.Manager.GetZoneName()
}
func (s *SBaseStorage) GetCapacity() int {
return s.GetAvailSizeMb()
}
func (s *SBaseStorage) GetStorageConf() *jsonutils.JSONDict {
return s.StorageConf
}
func (s *SBaseStorage) GetAvailSizeMb() int {
return s.GetTotalSizeMb()
}
func (s *SBaseStorage) GetMediumType() string {
return s.Manager.GetMediumType()
}
func (s *SBaseStorage) GetFreeSizeMb() int {
var stat syscall.Statfs_t
err := syscall.Statfs(s.Path, &stat)
if err != nil {
log.Errorln(err)
return -1
}
return int(stat.Bavail * uint64(stat.Bsize) / 1024 / 1024)
}
func (s *SBaseStorage) GetTotalSizeMb() int {
var stat syscall.Statfs_t
err := syscall.Statfs(s.Path, &stat)
if err != nil {
log.Errorln(err)
return -1
}
return int(stat.Blocks * uint64(stat.Bsize) / 1024 / 1024)
}
func (s *SBaseStorage) SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject) {
s.StorageId = storageId
s.StorageName = storageName
if dconf, ok := conf.(*jsonutils.JSONDict); ok {
s.StorageConf = dconf
}
}
func (s *SBaseStorage) RemoveDisk(d IDisk) {
s.DiskLock.Lock()
defer s.DiskLock.Unlock()
for i := 0; i < len(s.Disks); i++ {
if s.Disks[i].GetId() == d.GetId() {
s.Disks = append(s.Disks[:i], s.Disks[i+1:]...)
break
}
}
}
func (s *SBaseStorage) DeleteDiskfile(diskpath string) error {
return fmt.Errorf("Not Implement")
}
func (s *SBaseStorage) CreateDiskByDiskinfo(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
createParams, ok := params.(*SDiskCreateByDiskinfo)
if !ok {
return nil, hostutils.ParamsError
}
if createParams.Disk != nil {
if !jsonutils.QueryBoolean(createParams.DiskInfo, "rebuild", false) {
return nil, fmt.Errorf("Disk exist")
}
if err := createParams.Disk.OnRebuildRoot(ctx, createParams.DiskInfo); err != nil {
return nil, err
}
}
disk := createParams.Storage.CreateDisk(createParams.DiskId)
if disk == nil {
return nil, fmt.Errorf("Fail to Create disk %s", createParams.DiskId)
}
switch {
case createParams.DiskInfo.Contains("snapshot"):
log.Infof("CreateDiskFromSnpashot %s", createParams)
return s.CreateDiskFromSnpashot(ctx, disk, createParams)
case createParams.DiskInfo.Contains("image_id"):
log.Infof("CreateDiskFromTemplate %s", createParams)
return s.CreateDiskFromTemplate(ctx, disk, createParams)
case createParams.DiskInfo.Contains("size"):
log.Infof("CreateRawDisk %s", createParams)
return s.CreateRawDisk(ctx, disk, createParams)
default:
return nil, fmt.Errorf("Not fount")
}
}
func (s *SBaseStorage) CreateRawDisk(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) {
size, _ := createParams.DiskInfo.Int("size")
diskFromat, _ := createParams.DiskInfo.GetString("format")
fsFormat, _ := createParams.DiskInfo.GetString("fs_format")
encryption := jsonutils.QueryBoolean(createParams.DiskInfo, "encryption", false)
return disk.CreateRaw(ctx, int(size), diskFromat, fsFormat, encryption, createParams.DiskId, "")
}
func (s *SBaseStorage) CreateDiskFromTemplate(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) {
var (
imageId, _ = createParams.DiskInfo.GetString("image_id")
format = "qcow2" // force qcow2
size, _ = createParams.DiskInfo.Int("size")
)
return disk.CreateFromTemplate(ctx, imageId, format, size)
}
func (s *SBaseStorage) CreateDiskFromSnpashot(ctx context.Context, disk IDisk, createParams *SDiskCreateByDiskinfo) (jsonutils.JSONObject, error) {
var storage = createParams.Storage
var snapshotUrl, _ = createParams.DiskInfo.GetString("snapshot_url")
if len(snapshotUrl) == 0 {
return nil, fmt.Errorf("Create disk from snapshot missing params snapshot url")
}
if err := storage.CreateDiskFromSnapshot(ctx, disk, createParams); err != nil {
return nil, err
}
return disk.GetDiskDesc(), nil
}
func (s *SBaseStorage) DestinationPrepareMigrate(
ctx context.Context, liveMigrate bool, disksUri string, snapshotsUri string,
desc, disksBackingFile, srcSnapshots jsonutils.JSONObject, rebaseDisks bool,
) error {
return nil
}
/*************************Background delete snapshot job****************************/
func StartSnapshotRecycle(storage IStorage) {
log.Infof("Snapshot recyle job started")
if !fileutils2.Exists(storage.GetSnapshotDir()) {
procutils.NewCommand("mkdir", "-p", storage.GetSnapshotDir()).Run()
}
cronman.GetCronJobManager().AddJobAtIntervals(
"SnapshotRecycle", time.Hour*6,
func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
snapshotRecycle(ctx, userCred, isStart, storage)
})
}
func StorageRequestSnapshotRecycle(ctx context.Context, userCred mcclient.TokenCredential, storage IStorage) {
defer func() {
if r := recover(); r != nil {
log.Errorf("On storage request snapshot recycle %s \n %s", r, debug.Stack())
}
}()
if !fileutils2.Exists(storage.GetSnapshotDir()) {
procutils.NewCommand("mkdir", "-p", storage.GetSnapshotDir()).Run()
}
snapshotRecycle(ctx, userCred, false, storage)
}
func snapshotRecycle(ctx context.Context, userCred mcclient.TokenCredential, isStart bool, storage IStorage) {
log.Infof("Snapshot Recycle Job Start, storage is %s, ss dir is %s", storage.GetStorageName(), storage.GetSnapshotDir())
res, err := modules.Snapshots.GetById(hostutils.GetComputeSession(ctx), "max-count", nil)
if err != nil {
log.Errorln(err)
return
}
maxSnapshotCount, err := res.Int("max_count")
if err != nil {
log.Errorln("Request region get snapshot max count failed")
return
}
files, err := ioutil.ReadDir(storage.GetSnapshotDir())
if err != nil {
log.Errorln(err)
return
}
for _, file := range files {
checkSnapshots(storage, file.Name(), int(maxSnapshotCount))
}
}
func checkSnapshots(storage IStorage, snapshotDir string, maxSnapshotCount int) {
re := regexp.MustCompile(`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}_snap$`)
if !re.MatchString(snapshotDir) {
log.Warningf("snapshot_dir got unexcept file %s", snapshotDir)
return
}
diskId := snapshotDir[:len(snapshotDir)-len(options.HostOptions.SnapshotDirSuffix)]
snapshotPath := path.Join(storage.GetSnapshotDir(), snapshotDir)
// If disk is Deleted, request delete this disk all snapshots
if !fileutils2.Exists(path.Join(storage.GetPath(), diskId)) && fileutils2.Exists(snapshotPath) {
params := jsonutils.NewDict()
params.Set("disk_id", jsonutils.NewString(diskId))
_, err := modules.Snapshots.PerformClassAction(
hostutils.GetComputeSession(context.Background()),
"delete-disk-snapshots", params)
if err != nil {
log.Infof("Request delele disk %s snapshots failed %s", diskId, err)
}
return
}
snapshots, err := ioutil.ReadDir(snapshotPath)
if err != nil {
log.Errorln(err)
return
}
// if snapshot count greater than maxsnapshot count, do convert
if len(snapshots) >= maxSnapshotCount {
requestConvertSnapshot(storage, snapshotPath, diskId)
}
}
func requestConvertSnapshot(storage IStorage, snapshotPath, diskId string) {
log.Infof("SNPASHOT path %s", snapshotPath)
res, err := modules.Disks.GetSpecific(
hostutils.GetComputeSession(context.Background()), diskId, "convert-snapshot", nil)
if err != nil {
log.Errorln(err)
return
}
var (
deleteSnapshot, _ = res.GetString("delete_snapshot")
convertSnapshot, _ = res.GetString("convert_snapshot")
pendingDelete, _ = res.Bool("pending_delete")
)
log.Infof("start convert disk(%s) snapshot(%s), delete_snapshot is %s",
diskId, convertSnapshot, deleteSnapshot)
convertSnapshotPath := path.Join(snapshotPath, convertSnapshot)
outfile := convertSnapshotPath + ".tmp"
img, err := qemuimg.NewQemuImage(convertSnapshotPath)
if err != nil {
log.Errorln(err)
return
}
log.Infof("convertSnapshot path %s", convertSnapshotPath)
err = img.Convert2Qcow2To(outfile, true)
if err != nil {
log.Errorln(err)
return
}
requestDeleteSnapshot(
storage, diskId, snapshotPath, deleteSnapshot,
convertSnapshotPath, outfile, pendingDelete,
)
}
func requestDeleteSnapshot(
storage IStorage, diskId, snapshotPath, deleteSnapshot, convertSnapshotPath,
outfile string, pendingDelete bool,
) {
deleteSnapshotPath := path.Join(snapshotPath, deleteSnapshot)
DELETEING_SNAPSHOTS.Store(diskId, true)
defer DELETEING_SNAPSHOTS.Delete(diskId)
_, err := modules.Snapshots.PerformAction(hostutils.GetComputeSession(context.Background()),
deleteSnapshot, "deleted", nil)
if err != nil {
log.Errorln(err)
return
}
if out, err := procutils.NewCommand("rm", "-f", convertSnapshotPath).Output(); err != nil {
log.Errorf("%s", out)
return
}
if out, err := procutils.NewCommand("mv", "-f", outfile, convertSnapshotPath).Output(); err != nil {
log.Errorf("%s", out)
return
}
if !pendingDelete {
if err := storage.DeleteDiskfile(deleteSnapshotPath); err != nil {
log.Errorln(err)
return
}
}
}