fix(hostman): rbd command

This commit is contained in:
Qu Xuan
2021-09-17 14:20:32 +08:00
parent aab33d1355
commit dbee3ec258
8 changed files with 704 additions and 495 deletions
-1
View File
@@ -4,6 +4,5 @@ MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com"
ENV TZ Asia/Shanghai
RUN apk add librados librbd
RUN mkdir -p /opt/yunion/bin
ADD ./_output/alpine-build/bin/host /opt/yunion/bin/host
-1
View File
@@ -13,6 +13,5 @@ MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com"
ENV TZ Asia/Shanghai
RUN apk add librados librbd
RUN mkdir -p /opt/yunion/bin
COPY --from=build /root/go/src/yunion.io/x/onecloud/_output/bin/host /opt/yunion/bin/host
+1 -1
View File
@@ -846,7 +846,7 @@ func (s *SKVMGuestInstance) DeployFs(deployInfo *deployapi.DeployInfo) (jsonutil
diskPath, _ := disks[0].GetString("path")
disk := storageman.GetManager().GetDiskByPath(diskPath)
if disk == nil {
return nil, fmt.Errorf("Cannot find disk index 0")
return nil, fmt.Errorf("Cannot find disk %s index 0", diskPath)
}
return disk.DeployGuestFs(disk.GetPath(), s.Desc, deployInfo)
} else {
+11 -9
View File
@@ -20,8 +20,6 @@ import (
"context"
"fmt"
"github.com/ceph/go-ceph/rbd"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
@@ -29,6 +27,7 @@ import (
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
)
@@ -48,12 +47,14 @@ func (d *SRBDDisk) GetType() string {
func (d *SRBDDisk) Probe() error {
storage := d.Storage.(*SRbdStorage)
storageConf := d.Storage.GetStorageConf()
pool, _ := storageConf.GetString("pool")
_, err := storage.withImage(pool, d.Id, func(image *rbd.Image) (interface{}, error) {
return image.GetSize()
})
return err
exist, err := storage.IsImageExist(d.Id)
if err != nil {
return errors.Wrapf(err, "IsImageExist")
}
if !exist {
return cloudprovider.ErrNotFound
}
return nil
}
func (d *SRBDDisk) getPath() string {
@@ -75,11 +76,12 @@ func (d *SRBDDisk) GetDiskDesc() jsonutils.JSONObject {
storage := d.Storage.(*SRbdStorage)
storageConf := d.Storage.GetStorageConf()
pool, _ := storageConf.GetString("pool")
sizeMb, _ := storage.getImageSizeMb(pool, d.Id)
desc := map[string]interface{}{
"disk_id": d.Id,
"disk_format": "raw",
"disk_path": d.GetPath(),
"disk_size": storage.getImageSizeMb(pool, d.Id),
"disk_size": sizeMb,
}
return jsonutils.Marshal(desc)
}
+1 -1
View File
@@ -113,7 +113,7 @@ func (r *SRbdImageCache) GetDesc() *remotefile.SImageDesc {
imageCacheManger := r.Manager.(*SRbdImageCacheManager)
storage := imageCacheManger.storage.(*SRbdStorage)
size := storage.getImageSizeMb(imageCacheManger.Pool, r.GetName())
size, _ := storage.getImageSizeMb(imageCacheManger.Pool, r.GetName())
return &remotefile.SImageDesc{
Size: int64(size),
Name: r.imageName,
+211 -482
View File
@@ -21,24 +21,21 @@ import (
"fmt"
"os"
"strings"
"time"
"github.com/ceph/go-ceph/rados"
"github.com/ceph/go-ceph/rbd"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudprovider"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/cephutils"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/qemutils"
)
@@ -48,22 +45,24 @@ const (
RBD_ORDER = 22 //为rbd对应到rados中每个对象的大小,默认为4MB
)
var (
ErrNoSuchImage = errors.New("no such image")
ErrNoSuchSnapshot = errors.New("no such snapshot")
)
type sStorageConf struct {
MonHost string
Key string
Pool string
RadosMonOpTimeout int64
RadosOsdOpTimeout int64
ClientMountTimeout int64
}
type SRbdStorage struct {
SBaseStorage
sStorageConf
}
func NewRBDStorage(manager *SStorageManager, path string) *SRbdStorage {
var ret = new(SRbdStorage)
ret.SBaseStorage = *NewBaseStorage(manager, path)
err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", "/etc/ceph").Run()
if err != nil {
log.Errorf("Failed to mkdir /etc/ceph: %s", err)
}
ret.sStorageConf = sStorageConf{}
return ret
}
@@ -90,28 +89,21 @@ func (s *SRbdStorage) GetSnapshotPathByIds(diskId, snapshotId string) string {
return ""
}
func (s *SRbdStorage) GetClient() (*cephutils.CephClient, error) {
return cephutils.NewClient(s.MonHost, s.Key, s.Pool)
}
func (s *SRbdStorage) IsSnapshotExist(diskId, snapshotId string) (bool, error) {
var exist bool
pool, _ := s.StorageConf.GetString("pool")
_, err := s.withImage(pool, diskId,
func(src *rbd.Image) (interface{}, error) {
sps, err := src.GetSnapshotNames()
if err != nil {
return nil, errors.Wrap(err, "get snapshot names")
}
for i := 0; i < len(sps); i++ {
if sps[i].Name == snapshotId {
exist = true
break
}
}
return nil, nil
},
)
client, err := s.GetClient()
if err != nil {
return false, err
return false, errors.Wrapf(err, "GetClient")
}
return exist, nil
defer client.Close()
img, err := client.GetImage(diskId)
if err != nil {
return false, errors.Wrapf(err, "GetImage")
}
return img.IsSnapshotExist(snapshotId)
}
func (s *SRbdStorage) GetSnapshotDir() string {
@@ -132,488 +124,217 @@ func (s *SRbdStorage) GetImgsaveBackupPath() string {
//Tip Configuration values containing :, @, or = can be escaped with a leading \ character.
func (s *SRbdStorage) getStorageConfString() string {
conf := ""
for _, key := range []string{"mon_host", "key"} {
if value, _ := s.StorageConf.GetString(key); len(value) > 0 {
if key == "mon_host" {
value = strings.Replace(value, ",", `\;`, -1)
}
for _, keyworkd := range []string{":", "@", "="} {
if strings.Index(value, keyworkd) != -1 {
value = strings.Replace(value, keyworkd, fmt.Sprintf(`\%s`, keyworkd), -1)
}
}
conf += fmt.Sprintf(":%s=%s", key, value)
conf := []string{}
conf = append(conf, "mon_host="+strings.ReplaceAll(s.MonHost, ",", `\;`))
key := s.Key
if len(key) > 0 {
for _, k := range []string{":", "@", "="} {
key = strings.ReplaceAll(key, k, fmt.Sprintf(`\%s`, k))
}
conf = append(conf, "key="+key)
}
for key, _timeout := range map[string]int64{
"rados_mon_op_timeout": api.RBD_DEFAULT_MON_TIMEOUT,
"rados_osd_op_timeout": api.RBD_DEFAULT_OSD_TIMEOUT,
"client_mount_timeout": api.RBD_DEFAULT_MOUNT_TIMEOUT,
for k, timeout := range map[string]int64{
"rados_mon_op_timeout": s.RadosMonOpTimeout,
"rados_osd_op_timeout": s.RadosOsdOpTimeout,
"client_mount_timeout": s.ClientMountTimeout,
} {
if timeout, _ := s.StorageConf.Int(key); timeout > 0 {
conf += fmt.Sprintf(":%s=%d", key, timeout)
} else {
conf += fmt.Sprintf(":%s=%d", key, _timeout)
}
conf = append(conf, fmt.Sprintf("%s=%d", k, timeout))
}
return conf
return ":" + strings.Join(conf, ":")
}
func (s *SRbdStorage) getImageSizeMb(pool string, name string) uint64 {
size, err := s.withImage(pool, name, func(image *rbd.Image) (interface{}, error) {
size, err := image.GetSize()
if err != nil {
return nil, err
}
return size / 1024 / 1024, nil
})
func (s *SRbdStorage) listImages(pool string) ([]string, error) {
client, err := s.GetClient()
if err != nil {
log.Errorf("get image error: %v", err)
return 0
return nil, errors.Wrapf(err, "GetClient")
}
return size.(uint64)
client.SetPool(pool)
defer client.Close()
return client.ListImages()
}
func (s *SRbdStorage) IsImageExist(name string) (bool, error) {
images, err := s.listImages(s.Pool)
if err != nil {
return false, errors.Wrapf(err, "listImages")
}
if utils.IsInStringArray(name, images) {
return true, nil
}
return false, nil
}
func (s *SRbdStorage) getImageSizeMb(pool string, name string) (uint64, error) {
client, err := s.GetClient()
if err != nil {
return 0, errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(pool)
img, err := client.GetImage(name)
if err != nil {
return 0, errors.Wrapf(err, "GetImage")
}
info, err := img.GetInfo()
if err != nil {
return 0, errors.Wrapf(err, "GetInfo")
}
return uint64(info.SizeByte) / 1024 / 1024, nil
}
func (s *SRbdStorage) resizeImage(pool string, name string, sizeMb uint64) error {
_, err := s.withImage(pool, name, func(image *rbd.Image) (interface{}, error) {
return nil, image.Resize(sizeMb * 1024 * 1024)
})
return err
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(pool)
img, err := client.GetImage(name)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
return img.Resize(int64(sizeMb))
}
func (s *SRbdStorage) deleteImage(pool string, name string) error {
_, err := s.withIOContext(pool, func(ioctx *rados.IOContext) (interface{}, error) {
names, err := rbd.GetImageNames(ioctx)
if err != nil {
return nil, err
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(pool)
img, err := client.GetImage(name)
if err != nil {
if errors.Cause(err) == cloudprovider.ErrNotFound {
return nil
}
if !utils.IsInStringArray(name, names) {
return nil, nil
}
image := rbd.GetImage(ioctx, name)
err = image.Open()
if err != nil {
return nil, errors.Wrap(err, "image.Open()")
}
//需要先删除image底下的snap
snapInfos, err := image.GetSnapshotNames()
if err != nil {
return nil, errors.Wrap(err, "image.GetSnapshotNames()")
}
for _, snapInfo := range snapInfos {
image.Close()
err = image.Open(snapInfo.Name)
if err != nil {
return nil, errors.Wrapf(err, "image.Open(%s)", snapInfo.Name)
}
pools, images, err := image.ListChildren()
if err != nil {
return nil, errors.Wrap(err, "image.ListChildren")
}
for i, _pool := range pools {
//需要解除snap底下的image关系
_, err = s.withIOContext(_pool, func(ioctx *rados.IOContext) (interface{}, error) {
_image := rbd.GetImage(ioctx, images[i])
err = _image.Open()
if err != nil {
return nil, errors.Wrap(err, "_image.Open()")
}
defer _image.Close()
log.Debugf("start flatten %s/%s@%s => %s/%s", pool, name, snapInfo.Name, _pool, images[i])
err := _image.Flatten()
if err != nil {
return nil, errors.Wrap(err, "_image.Flatten")
}
return nil, nil
})
if err != nil {
return nil, errors.Wrapf(err, "flatten child %s/%s", _pool, images[i])
}
}
snapshot := image.GetSnapshot(snapInfo.Name)
protect, err := snapshot.IsProtected()
if err != nil {
return nil, errors.Wrapf(err, "snapshot.IsProtected() %s", snapInfo.Name)
}
if protect {
for i := 0; i < 3; i++ {
err = snapshot.Unprotect()
if err == nil {
break
}
//Resource busy
if strings.Contains(err.Error(), "16") {
log.Warningf("snapshot is busy, try unprotect after %d seconds", (i+1)*5)
time.Sleep(time.Second * time.Duration(i+1) * 5)
continue
}
return nil, errors.Wrapf(err, "snapshot.Unprotect() %s", snapInfo.Name)
}
}
err = snapshot.Remove()
if err != nil {
return nil, errors.Wrapf(err, "snapshot.Remove() %s", snapInfo.Name)
}
}
image.Close()
err = image.Remove()
if err != nil {
return nil, errors.Wrapf(err, "image.Remove() %s/%s", pool, name)
}
return nil, nil
})
return err
}
// 比较费时
func (s *SRbdStorage) copyImage(srcPool string, srcImage string, destPool string, destImage string) error {
_, err := s.withImage(srcPool, srcImage, func(src *rbd.Image) (interface{}, error) {
imageSize, err := src.GetSize()
if err != nil {
return nil, err
}
if err := s.createImage(destPool, destImage, imageSize/1024/1024); err != nil {
log.Errorf("create image dest pool: %s dest image: %s image size: %dMb error: %v", destPool, destImage, imageSize/1024/1024, err)
return nil, err
}
_, err = s.withImage(destPool, destImage, func(dest *rbd.Image) (interface{}, error) {
return nil, src.Copy(*dest)
})
return nil, err
})
return err
return errors.Wrapf(err, "GetImage")
}
return img.Delete()
}
// 速度快
func (s *SRbdStorage) cloneImage(ctx context.Context, srcPool string, srcImage string, destPool string, destImage string) error {
_, err := s.withImage(srcPool, srcImage, func(src *rbd.Image) (interface{}, error) {
err := func() error {
lockman.LockRawObject(ctx, "rbd_image_cache", srcImage)
defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", srcImage)
snapInfos, err := src.GetSnapshotNames()
if err != nil {
return errors.Wrap(err, "image.GetSnapshotNames()")
}
var snapshot *rbd.Snapshot = nil
for _, snap := range snapInfos {
if snap.Name == destImage {
snapshot = src.GetSnapshot(destImage)
break
}
}
if snapshot == nil {
snapshot, err = src.CreateSnapshot(destImage)
if err != nil {
return errors.Wrap(err, "src.CreateSnapshot")
}
}
isProtect, err := snapshot.IsProtected()
if err != nil {
return errors.Wrap(err, "snapshot.IsProtected")
}
if !isProtect {
if err := snapshot.Protect(); err != nil {
return errors.Wrap(err, "snapshot.Protect")
}
}
return nil
}()
if err != nil {
return nil, errors.Wrap(err, "get snapshot")
}
return s.withIOContext(destPool, func(ioctx *rados.IOContext) (interface{}, error) {
_, err := src.Clone(destImage, ioctx, destImage, RBD_FEATURE, RBD_ORDER)
if err != nil {
return nil, errors.Wrapf(err, "src.Clone")
}
return nil, nil
})
})
return err
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(srcPool)
img, err := client.GetImage(srcImage)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
return img.Clone(ctx, destPool, destImage)
}
func (s *SRbdStorage) cloneFromSnapshot(srcImage, srcPool, srcSnapshot, newImage, pool string) error {
_, err := s.withImage(srcPool, srcImage, func(src *rbd.Image) (interface{}, error) {
snapshot := src.GetSnapshot(srcSnapshot)
isProtect, err := snapshot.IsProtected()
if err != nil {
return nil, errors.Wrap(err, "snapshot is protected")
}
if !isProtect {
if err := snapshot.Protect(); err != nil {
return nil, errors.Wrap(err, "snapshot protect")
}
defer snapshot.Unprotect()
}
return s.withIOContext(pool, func(ioctx *rados.IOContext) (interface{}, error) {
_, err := src.Clone(srcSnapshot, ioctx, newImage, RBD_FEATURE, RBD_ORDER)
if err != nil {
return nil, errors.Wrap(err, "clone from snapshot")
}
return nil, nil
})
})
client, err := s.GetClient()
if err != nil {
return errors.Wrap(err, "clone from snapshot")
return errors.Wrapf(err, "GetClient")
}
return nil
}
func (s *SRbdStorage) withImage(pool string, name string, doFunc func(*rbd.Image) (interface{}, error)) (interface{}, error) {
return s.withIOContext(pool, func(ioctx *rados.IOContext) (interface{}, error) {
image := rbd.GetImage(ioctx, name)
if err := image.Open(); err != nil {
log.Errorf("open image %s name error: %v", name, err)
return nil, err
}
defer image.Close()
return doFunc(image)
})
}
func (s *SRbdStorage) withIOContext(pool string, doFunc func(*rados.IOContext) (interface{}, error)) (interface{}, error) {
return s.withCluster(func(conn *rados.Conn) (interface{}, error) {
ioctx, err := conn.OpenIOContext(pool)
if err != nil {
return nil, errors.Wrapf(err, "conn.OpenIOContext(%s)", pool)
}
return doFunc(ioctx)
})
}
func (s *SRbdStorage) listImages(pool string) ([]string, error) {
images, err := s.withIOContext(pool, func(ioctx *rados.IOContext) (interface{}, error) {
return rbd.GetImageNames(ioctx)
})
defer client.Close()
client.SetPool(srcPool)
img, err := client.GetImage(srcImage)
if err != nil {
return nil, err
return errors.Wrapf(err, "GetImage")
}
return images.([]string), nil
}
func (s *SRbdStorage) withCluster(doFunc func(*rados.Conn) (interface{}, error)) (interface{}, error) {
conn, _ := rados.NewConn()
for _, key := range []string{"mon_host", "key"} {
if value, _ := s.StorageConf.GetString(key); len(value) > 0 {
if err := conn.SetConfigOption(key, value); err != nil {
return nil, err
}
}
snap, err := img.GetSnapshot(srcSnapshot)
if err != nil {
return errors.Wrapf(err, "GetSnapshot")
}
for key, timeout := range map[string]int64{
"rados_osd_op_timeout": api.RBD_DEFAULT_OSD_TIMEOUT,
"rados_mon_op_timeout": api.RBD_DEFAULT_MON_TIMEOUT,
"client_mount_timeout": api.RBD_DEFAULT_MOUNT_TIMEOUT,
} {
_timeout, _ := s.StorageConf.Int(key)
if _timeout > 0 {
timeout = _timeout
}
if err := conn.SetConfigOption(key, fmt.Sprintf("%d", timeout)); err != nil {
return nil, err
}
}
if err := conn.Connect(); err != nil {
return nil, errors.Wrapf(err, "conn.Connect() %s", s.StorageName)
}
defer conn.Shutdown()
return doFunc(conn)
return snap.Clone(pool, newImage)
}
func (s *SRbdStorage) createImage(pool string, name string, sizeMb uint64) error {
_, err := s.withIOContext(pool, func(ioctx *rados.IOContext) (interface{}, error) {
image, err := rbd.Create(ioctx, name, sizeMb*1024*1024, RBD_ORDER, RBD_FEATURE)
if err != nil {
return nil, err
}
defer image.Close()
return nil, nil
})
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(pool)
_, err = client.CreateImage(name, int64(sizeMb))
return err
}
func (s *SRbdStorage) renameImage(pool string, src string, dest string) error {
_, err := s.withImage(pool, src, func(image *rbd.Image) (interface{}, error) {
return nil, image.Rename(dest)
})
return err
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
client.SetPool(pool)
img, err := client.GetImage(src)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
return img.Rename(dest)
}
func (s *SRbdStorage) resetDisk(pool string, diskId string, snapshotId string) error {
_, err := s.withImage(pool, diskId, func(image *rbd.Image) (interface{}, error) {
snap := image.GetSnapshot(snapshotId)
return nil, snap.Rollback()
})
return err
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
client.SetPool(pool)
defer client.Close()
img, err := client.GetImage(diskId)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
snap, err := img.GetSnapshot(snapshotId)
if err != nil {
return errors.Wrapf(err, "GetSnapshot")
}
return snap.Rollback()
}
func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId string) error {
_, err := s.withImage(pool, diskId, func(image *rbd.Image) (interface{}, error) {
return image.CreateSnapshot(snapshotId)
})
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
client.SetPool(pool)
defer client.Close()
img, err := client.GetImage(diskId)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
_, err = img.CreateSnapshot(snapshotId)
return err
}
func (s *SRbdStorage) deleteSnapshot(pool string, diskId string, snapshotId string) error {
_, err := s.withImage(pool, diskId, func(image *rbd.Image) (interface{}, error) {
snapshots, err := image.GetSnapshotNames()
if err != nil {
return nil, errors.Wrapf(err, "image.GetSnapshotNames()")
}
var snapshot *rbd.Snapshot
for _, snapInfo := range snapshots {
if snapInfo.Name == snapshotId {
snapshot = image.GetSnapshot(snapshotId)
break
}
}
if snapshot == nil { //not found snapshot
return nil, nil
}
isProtect, err := snapshot.IsProtected()
if err != nil {
return nil, errors.Wrap(err, "snapshot is protected")
}
if isProtect {
image.Close()
if err := image.Open(snapshotId); err != nil {
return nil, errors.Wrap(err, "image open snapshot")
}
pools, childImgs, err := image.ListChildren()
if err != nil {
return nil, errors.Wrap(err, "image list children")
}
for i, iPool := range pools {
_, err = s.withIOContext(iPool, func(ioctx *rados.IOContext) (interface{}, error) {
_image := rbd.GetImage(ioctx, childImgs[i])
err = _image.Open()
if err != nil {
return nil, errors.Wrap(err, "open child image")
}
defer _image.Close()
log.Infof("start flatten %s/%s@%s => %s/%s", pool, diskId, snapshotId, iPool, childImgs[i])
err := _image.Flatten()
if err != nil {
return nil, errors.Wrap(err, "child image flatten")
}
return nil, nil
})
if err != nil {
return nil, errors.Wrapf(err, "flatten child %s/%s", iPool, childImgs[i])
}
}
for i := 0; i < 3; i++ {
err = snapshot.Unprotect()
if err == nil {
break
}
//Resource busy
if strings.Contains(err.Error(), "16") {
log.Warningf("snapshot is busy, try unprotect after %d seconds", (i+1)*5)
time.Sleep(time.Second * time.Duration(i+1) * 5)
continue
}
return nil, errors.Wrapf(err, "snapshot.Unprotect() %s", snapshotId)
}
}
err = snapshot.Remove()
if err != nil {
return nil, errors.Wrap(err, "snapshot remove")
}
return nil, nil
})
return err
}
type SMonCommand struct {
Prefix string
Pool string
Format string
}
type sCephCapacity struct {
CapacitySizeByte int64
UsedCapacitySizeByte int64
}
type sCephDfResult struct {
Stats struct {
TotalBytes int64 `json:"total_bytes"`
TotalAvailBytes int64 `json:"total_avail_bytes"`
TotalUsedBytes int64 `json:"total_used_bytes"`
TotalUsedRawBytes int64 `json:"total_used_raw_bytes"`
TotalUsedRawRatio float64 `json:"total_used_raw_ratio"`
NumOsds int `json:"num_osds"`
NumPerPoolOsds int `json:"num_per_pool_osds"`
}
Pools []struct {
Name string `json:"name"`
ID int `json:"id"`
Stats struct {
Stored int `json:"stored"`
Objects int `json:"objects"`
KbUsed int `json:"kb_used"`
BytesUsed int64 `json:"bytes_used"`
PercentUsed int `json:"percent_used"`
MaxAvail int64 `json:"max_avail"`
} `json:"stats"`
}
}
func (s *SRbdStorage) getCapacity() (*sCephCapacity, error) {
ret := &sCephCapacity{}
_, err := s.withCluster(func(conn *rados.Conn) (interface{}, error) {
poolName, _ := s.StorageConf.GetString("pool")
cmd := SMonCommand{Prefix: "df", Format: "json"}
buffer, _, err := conn.MonCommand([]byte(jsonutils.Marshal(cmd).String()))
if err != nil {
return nil, errors.Wrapf(err, "MonCommand")
}
result, err := jsonutils.Parse(buffer)
if err != nil {
return nil, errors.Wrapf(err, string(buffer))
}
df := &sCephDfResult{}
result.Unmarshal(df)
ret.CapacitySizeByte = df.Stats.TotalAvailBytes
ret.UsedCapacitySizeByte = df.Stats.TotalUsedBytes
for _, pool := range df.Pools {
if pool.Name == poolName {
ret.UsedCapacitySizeByte = pool.Stats.BytesUsed
}
}
return nil, nil
})
client, err := s.GetClient()
if err != nil {
return ret, errors.Wrap(err, "getCapacity")
return errors.Wrapf(err, "GetClient")
}
return ret, nil
client.SetPool(pool)
defer client.Close()
img, err := client.GetImage(diskId)
if err != nil {
return errors.Wrapf(err, "GetImage")
}
snap, err := img.GetSnapshot(snapshotId)
if err != nil {
return errors.Wrapf(err, "GetSnapshot")
}
return snap.Delete()
}
func (s *SRbdStorage) SyncStorageSize() error {
content := jsonutils.NewDict()
capacity, err := s.getCapacity()
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "getCapacity")
return errors.Wrapf(err, "GetClient")
}
content.Set("capacity", jsonutils.NewInt(int64(capacity.CapacitySizeByte/1024/1024)))
content.Set("actual_capacity_used", jsonutils.NewInt(int64(capacity.UsedCapacitySizeByte/1024/1024)))
defer client.Close()
capacity, err := client.GetCapacity()
if err != nil {
return errors.Wrapf(err, "GetCapacity")
}
content.Set("capacity", jsonutils.NewInt(int64(capacity.CapacitySizeKb/1024)))
content.Set("actual_capacity_used", jsonutils.NewInt(int64(capacity.UsedCapacitySizeKb/1024)))
_, err = modules.Storages.Put(
hostutils.GetComputeSession(context.Background()),
s.StorageId, content)
@@ -623,14 +344,20 @@ func (s *SRbdStorage) SyncStorageSize() error {
func (s *SRbdStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
content := map[string]interface{}{}
if len(s.StorageId) > 0 {
capacity, err := s.getCapacity()
client, err := s.GetClient()
if err != nil {
return modules.Storages.PerformAction(hostutils.GetComputeSession(context.Background()), s.StorageId, "offline", nil)
}
defer client.Close()
capacity, err := client.GetCapacity()
if err != nil {
return modules.Storages.PerformAction(hostutils.GetComputeSession(context.Background()), s.StorageId, "offline", nil)
}
content = map[string]interface{}{
"name": s.StorageName,
"capacity": capacity.CapacitySizeByte / 1024 / 1024,
"actual_capacity_used": capacity.UsedCapacitySizeByte / 1024 / 1024,
"capacity": capacity.CapacitySizeKb / 1024,
"actual_capacity_used": capacity.UsedCapacitySizeKb / 1024,
"status": api.STORAGE_ONLINE,
"zone": s.GetZoneName(),
}
@@ -646,9 +373,6 @@ func (s *SRbdStorage) GetDiskById(diskId string) (IDisk, error) {
if s.Disks[i].GetId() == diskId {
err := s.Disks[i].Probe()
if err != nil {
if errors.Cause(err) == rbd.RbdErrorNotFound {
return nil, cloudprovider.ErrNotFound
}
return nil, errors.Wrapf(err, "disk.Prob")
}
return s.Disks[i], nil
@@ -671,20 +395,12 @@ func (s *SRbdStorage) CreateDisk(diskId string) IDisk {
}
func (s *SRbdStorage) Accessible() error {
var c = make(chan error)
go func() {
_, err := s.withCluster(func(conn *rados.Conn) (interface{}, error) {
return conn.ListPools()
})
c <- err
}()
var err error
select {
case err = <-c:
break
case <-time.After(time.Second * 30):
err = ErrStorageTimeout
client, err := s.GetClient()
if err != nil {
return errors.Wrapf(err, "GetClient")
}
defer client.Close()
_, err = client.GetCapacity()
return err
}
@@ -814,8 +530,21 @@ func (s *SRbdStorage) CreateDiskFromSnapshot(
func (s *SRbdStorage) SetStorageInfo(storageId, storageName string, conf jsonutils.JSONObject) error {
s.StorageId = storageId
s.StorageName = storageName
if gotypes.IsNil(conf) {
return fmt.Errorf("empty storage conf for storage %s(%s)", storageName, storageId)
}
if dconf, ok := conf.(*jsonutils.JSONDict); ok {
s.StorageConf = dconf
}
conf.Unmarshal(&s.sStorageConf)
if s.RadosMonOpTimeout == 0 {
s.RadosMonOpTimeout = api.RBD_DEFAULT_MON_TIMEOUT
}
if s.RadosOsdOpTimeout == 0 {
s.RadosOsdOpTimeout = api.RBD_DEFAULT_OSD_TIMEOUT
}
if s.ClientMountTimeout == 0 {
s.ClientMountTimeout = api.RBD_DEFAULT_MOUNT_TIMEOUT
}
return nil
}
+479
View File
@@ -0,0 +1,479 @@
// 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 cephutils
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/procutils"
)
type CephClient struct {
monHost string
key string
pool string
cephConf string
keyConf string
}
func (self *CephClient) Close() error {
if len(self.keyConf) > 0 {
os.Remove(self.keyConf)
}
return os.Remove(self.cephConf)
}
func (self *CephClient) SetPool(pool string) {
self.pool = pool
}
type cephStats struct {
Stats struct {
TotalBytes int64 `json:"total_bytes"`
TotalAvailBytes int64 `json:"total_avail_bytes"`
TotalUsedBytes int64 `json:"total_used_bytes"`
TotalUsedRawBytes int64 `json:"total_used_raw_bytes"`
TotalUsedRawRatio float64 `json:"total_used_raw_ratio"`
NumOsds int `json:"num_osds"`
NumPerPoolOsds int `json:"num_per_pool_osds"`
} `json:"stats"`
StatsByClass struct {
Hdd struct {
TotalBytes int64 `json:"total_bytes"`
TotalAvailBytes int64 `json:"total_avail_bytes"`
TotalUsedBytes int64 `json:"total_used_bytes"`
TotalUsedRawBytes int64 `json:"total_used_raw_bytes"`
TotalUsedRawRatio float64 `json:"total_used_raw_ratio"`
} `json:"hdd"`
} `json:"stats_by_class"`
Pools []struct {
Name string `json:"name"`
ID int `json:"id"`
Stats struct {
Stored int `json:"stored"`
Objects int `json:"objects"`
KbUsed int `json:"kb_used"`
BytesUsed int `json:"bytes_used"`
PercentUsed int `json:"percent_used"`
MaxAvail int64 `json:"max_avail"`
} `json:"stats"`
} `json:"pools"`
}
type SCapacity struct {
CapacitySizeKb int64
UsedCapacitySizeKb int64
}
func (self *CephClient) output(name string, opts []string) (jsonutils.JSONObject, error) {
opts = append([]string{"--format", "json"}, opts...)
resp, err := procutils.NewRemoteCommandAsFarAsPossible(name, opts...).Output()
if err != nil {
return nil, errors.Wrapf(err, "CreateImage %s", string(resp))
}
return jsonutils.Parse(resp)
}
func (self *CephClient) run(name string, opts []string) error {
output, err := procutils.NewRemoteCommandAsFarAsPossible(name, opts...).Output()
if err != nil {
return errors.Wrapf(err, "CreateImage %s", string(output))
}
return nil
}
func (self *CephClient) options() []string {
opts := []string{"--conf", self.cephConf}
if len(self.keyConf) > 0 {
opts = append(opts, []string{"--keyring", self.keyConf}...)
}
return opts
}
func (self *CephClient) CreateImage(name string, sizeMb int64) (*SImage, error) {
opts := self.options()
image := &SImage{name: name, client: self}
opts = append(opts, []string{"create", image.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...)
return image, self.run("rbd", opts)
}
func (self *CephClient) GetCapacity() (*SCapacity, error) {
result := &SCapacity{}
opts := self.options()
opts = append(opts, "df")
resp, err := self.output("ceph", opts)
if err != nil {
return nil, errors.Wrapf(err, "output")
}
stats := cephStats{}
err = resp.Unmarshal(&stats)
if err != nil {
return nil, errors.Wrapf(err, "ret.Unmarshal")
}
result.CapacitySizeKb = stats.Stats.TotalAvailBytes / 1024
result.UsedCapacitySizeKb = stats.Stats.TotalUsedBytes / 1024
for _, pool := range stats.Pools {
if pool.Name == self.pool {
result.UsedCapacitySizeKb = int64(pool.Stats.KbUsed)
}
}
return result, nil
}
func writeFile(pattern string, content string) (string, error) {
file, err := ioutil.TempFile("", pattern)
if err != nil {
return "", errors.Wrapf(err, "TempFile")
}
defer file.Close()
name := file.Name()
_, err = file.Write([]byte(content))
if err != nil {
return name, errors.Wrapf(err, "write")
}
return name, nil
}
func NewClient(monHost, key, pool string) (*CephClient, error) {
client := &CephClient{
monHost: monHost,
key: key,
pool: pool,
}
var err error
monHosts := []string{}
for _, monHost := range strings.Split(client.monHost, ",") {
monHosts = append(monHosts, fmt.Sprintf(`[%s]`, monHost))
}
conf := fmt.Sprintf(`[global]
mon host = %s
rados mon op timeout = 5
rados osd_op timeout = 1200
client mount timeout = 120
`, strings.Join(monHosts, ","))
client.cephConf, err = writeFile("ceph.*.conf", conf)
if err != nil {
return nil, errors.Wrapf(err, "write file")
}
if len(client.key) > 0 {
keyring := fmt.Sprintf(`[client.admin]
key = %s
`, client.key)
client.keyConf, err = writeFile("ceph.*.keyring", keyring)
if err != nil {
return nil, errors.Wrapf(err, "write keyring")
}
}
return client, nil
}
type SImage struct {
name string
client *CephClient
}
func (self *SImage) GetName() string {
return fmt.Sprintf("%s/%s", self.client.pool, self.name)
}
func (self *CephClient) ListImages() ([]string, error) {
result := []string{}
opts := self.options()
opts = append(opts, []string{"ls", self.pool}...)
resp, err := self.output("rbd", opts)
if err != nil {
return nil, err
}
err = resp.Unmarshal(&result)
if err != nil {
return nil, errors.Wrapf(err, "ret.Unmarshal")
}
return result, nil
}
func (self *CephClient) GetImage(name string) (*SImage, error) {
images, err := self.ListImages()
if err != nil {
return nil, errors.Wrapf(err, "ListImages")
}
if !utils.IsInStringArray(name, images) {
return nil, cloudprovider.ErrNotFound
}
return &SImage{name: name, client: self}, nil
}
type SImageInfo struct {
Name string `json:"name"`
ID string `json:"id"`
SizeByte int64 `json:"size"`
Objects int `json:"objects"`
Order int `json:"order"`
ObjectSize int `json:"object_size"`
SnapshotCount int `json:"snapshot_count"`
BlockNamePrefix string `json:"block_name_prefix"`
Format int `json:"format"`
Features []string `json:"features"`
OpFeatures []interface{} `json:"op_features"`
Flags []interface{} `json:"flags"`
CreateTimestamp string `json:"create_timestamp"`
AccessTimestamp string `json:"access_timestamp"`
ModifyTimestamp string `json:"modify_timestamp"`
}
func (self *SImage) options() []string {
return self.client.options()
}
func (self *SImage) GetInfo() (*SImageInfo, error) {
opts := self.options()
opts = append(opts, []string{"info", self.GetName()}...)
resp, err := self.client.output("rbd", opts)
if err != nil {
return nil, err
}
info := &SImageInfo{}
return info, resp.Unmarshal(info)
}
func (self *SImage) ListSnapshots() ([]SSnapshot, error) {
opts := self.options()
opts = append(opts, []string{"snap", "ls", self.GetName()}...)
resp, err := self.client.output("rbd", opts)
if err != nil {
return nil, err
}
result := []SSnapshot{}
err = resp.Unmarshal(&result)
if err != nil {
return nil, errors.Wrapf(err, "ret.Unmarshal")
}
for i := range result {
result[i].image = self
}
return result, nil
}
func (self *SImage) GetSnapshot(name string) (*SSnapshot, error) {
snaps, err := self.ListSnapshots()
if err != nil {
return nil, errors.Wrapf(err, "ListSnapshots")
}
for i := range snaps {
if snaps[i].Name == name {
return &snaps[i], nil
}
}
return nil, cloudprovider.ErrNotFound
}
func (self *SImage) IsSnapshotExist(name string) (bool, error) {
_, err := self.GetSnapshot(name)
if err != nil {
if errors.Cause(err) == cloudprovider.ErrNotFound {
return false, nil
}
return false, errors.Wrapf(err, "GetSnapshot")
}
return true, nil
}
type SSnapshot struct {
Name string
Id string
Size int64
Protected bool
Timestamp string
image *SImage
}
func (self *SSnapshot) Rollback() error {
if !self.Protected {
return nil
}
opts := self.options()
opts = append(opts, []string{"snap", "rollback", self.GetName()}...)
return self.image.client.run("rbd", opts)
}
func (self *SSnapshot) options() []string {
return self.image.options()
}
func (self *SSnapshot) GetName() string {
return fmt.Sprintf("%s@%s", self.image.GetName(), self.Name)
}
func (self *SSnapshot) Unprotect() error {
if !self.Protected {
return nil
}
opts := self.options()
opts = append(opts, []string{"snap", "unprotect", self.GetName()}...)
return self.image.client.run("rbd", opts)
}
func (self *SSnapshot) Protect() error {
if self.Protected {
return nil
}
opts := self.options()
opts = append(opts, []string{"snap", "protect", self.GetName()}...)
return self.image.client.run("rbd", opts)
}
func (self *SSnapshot) Remove() error {
opts := self.options()
opts = append(opts, []string{"snap", "rm", self.GetName()}...)
return self.image.client.run("rbd", opts)
}
func (self *SSnapshot) Delete() error {
pool := self.image.client.pool
defer self.image.client.SetPool(pool)
if self.Protected {
err := self.Unprotect()
if err != nil {
return errors.Wrapf(err, "Unprotect")
}
chidren, err := self.ListChildren()
if err != nil {
return errors.Wrapf(err, "")
}
for i := range chidren {
self.image.client.SetPool(chidren[i].Pool)
image, err := self.image.client.GetImage(chidren[i].Image)
if err != nil {
return errors.Wrapf(err, "GetImage(%s/%s)", chidren[i].Pool, chidren[i].Image)
}
err = image.Flatten()
if err != nil {
return errors.Wrapf(err, "Flatten")
}
}
}
return self.Remove()
}
type SChildren struct {
Pool string
PoolNamespace string
Image string
}
func (self *SSnapshot) ListChildren() ([]SChildren, error) {
opts := self.options()
opts = append(opts, []string{"children", self.GetName()}...)
resp, err := self.image.client.output("rbd", opts)
if err != nil {
return nil, errors.Wrapf(err, "ListChildren")
}
chidren := []SChildren{}
return chidren, resp.Unmarshal(&chidren)
}
func (self *SImage) Resize(sizeMb int64) error {
opts := self.options()
opts = append(opts, []string{"resize", self.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...)
return self.client.run("rbd", opts)
}
func (self *SImage) Remove() error {
opts := self.options()
opts = append(opts, []string{"rm", self.GetName()}...)
return self.client.run("rbd", opts)
}
func (self *SImage) Flatten() error {
opts := self.options()
opts = append(opts, []string{"flatten", self.GetName()}...)
return self.client.run("rbd", opts)
}
func (self *SImage) Delete() error {
snapshots, err := self.ListSnapshots()
if err != nil {
return errors.Wrapf(err, "ListSnapshots")
}
for i := range snapshots {
err := snapshots[i].Delete()
if err != nil {
return errors.Wrapf(err, "delete snapshot %s", snapshots[i].GetName())
}
}
return self.Remove()
}
func (self *SImage) Rename(name string) error {
opts := self.options()
opts = append(opts, []string{"rename", self.GetName(), fmt.Sprintf("%s/%s", self.client.pool, name)}...)
return self.client.run("rbd", opts)
}
func (self *SImage) CreateSnapshot(name string) (*SSnapshot, error) {
snap := &SSnapshot{Name: name, image: self}
opts := self.options()
opts = append(opts, []string{"snap", "create", snap.GetName()}...)
return snap, self.client.run("rbd", opts)
}
func (self *SImage) Clone(ctx context.Context, pool, name string) error {
lockman.LockRawObject(ctx, "rbd_image_cache", self.GetName())
defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", self.GetName())
var findOrCreateSnap = func() (*SSnapshot, error) {
snaps, err := self.ListSnapshots()
if err != nil {
return nil, errors.Wrapf(err, "ListSnapshots")
}
for i := range snaps {
if snaps[i].Name == name {
return &snaps[i], nil
}
}
snap, err := self.CreateSnapshot(name)
if err != nil {
return nil, errors.Wrapf(err, "CreateSnapshot")
}
return snap, nil
}
snap, err := findOrCreateSnap()
if err != nil {
return errors.Wrapf(err, "findOrCreateSnap")
}
return snap.Clone(pool, name)
}
func (self *SSnapshot) Clone(pool, name string) error {
err := self.Protect()
if err != nil {
return errors.Wrapf(err, "Protect")
}
opts := self.options()
opts = append(opts, []string{"clone", self.GetName(), fmt.Sprintf("%s/%s", pool, name)}...)
return self.image.client.run("rbd", opts)
}
+1
View File
@@ -0,0 +1 @@
package cephutils // import "yunion.io/x/onecloud/pkg/util/cephutils"