cubefs/blobstore/blobnode/core/disk/disk.go
mawei029 46703bdc75 refactor(blobnode): optimize the qos of blobnode
Signed-off-by: mawei029 <mawei2@oppo.com>
2023-11-08 12:37:10 +08:00

1100 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2022 The CubeFS Authors.
//
// 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 disk
import (
"context"
"math/rand"
"os"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
"time"
bnapi "github.com/cubefs/cubefs/blobstore/api/blobnode"
bncom "github.com/cubefs/cubefs/blobstore/blobnode/base"
"github.com/cubefs/cubefs/blobstore/blobnode/base/flow"
"github.com/cubefs/cubefs/blobstore/blobnode/base/qos"
"github.com/cubefs/cubefs/blobstore/blobnode/core"
"github.com/cubefs/cubefs/blobstore/blobnode/core/chunk"
myos "github.com/cubefs/cubefs/blobstore/blobnode/sys"
bloberr "github.com/cubefs/cubefs/blobstore/common/errors"
"github.com/cubefs/cubefs/blobstore/common/proto"
"github.com/cubefs/cubefs/blobstore/common/trace"
"github.com/cubefs/cubefs/blobstore/util/errors"
"github.com/cubefs/cubefs/blobstore/util/limit"
"github.com/cubefs/cubefs/blobstore/util/limit/keycount"
"github.com/cubefs/cubefs/blobstore/util/taskpool"
)
const (
MaxChunkSize = int64(1024 << 30) // 1024 GiB
RandomIntervalS = 30
)
var StateTransitionRules = map[bnapi.ChunkStatus][]bnapi.ChunkStatus{
bnapi.ChunkStatusDefault: {bnapi.ChunkStatusNormal},
bnapi.ChunkStatusNormal: {bnapi.ChunkStatusNormal, bnapi.ChunkStatusReadOnly},
bnapi.ChunkStatusReadOnly: {bnapi.ChunkStatusNormal, bnapi.ChunkStatusReadOnly, bnapi.ChunkStatusRelease},
}
var (
_chunkVer = []byte{0x1}
_diskVer = []byte{0x1}
)
type DiskStorageWrapper struct {
*DiskStorage
}
type DiskStorage struct {
DiskID proto.DiskID
Lock sync.RWMutex
SuperBlock *SuperBlock
Chunks map[proto.Vuid]core.ChunkAPI
// conf
Conf *core.Config
DataPath string
MetaPath string
// limiter
ChunkLimitPerKey limit.Limiter
// stats
stats atomic.Value // *core.DiskStats
// DataQos (include io visualization function)
dataQos qos.Qos
// status
status proto.DiskStatus
isMountPoint bool
closed bool
// chan
compactCh chan proto.Vuid
closeCh chan struct{}
// ctx is used for initiated requests that
// may need to be canceled on server shutdown.
wg sync.WaitGroup
ctx context.Context
// hook fn
OnClosed func()
CreateAt int64
LastUpdateAt int64
// io pools
writePool taskpool.IoPool
readPool taskpool.IoPool
}
func (ds *DiskStorage) IsRegister() bool {
return false
}
func (ds *DiskStorage) waitAllLoopsStop(ctx context.Context) {
span := trace.SpanFromContextSafe(ctx)
done := make(chan struct{})
go func() {
ds.wg.Wait()
close(done)
}()
warnTicker := time.NewTicker(30 * time.Second)
defer warnTicker.Stop()
for {
select {
case <-warnTicker.C:
span.Warnf("=== disk<%v> loop wait timed out. ===", ds.DiskID)
case <-done:
span.Infof("=== disk<%v> all loops done ===", ds.DiskID)
return
}
}
}
func (ds *DiskStorage) Close(ctx context.Context) {
span := trace.SpanFromContextSafe(ctx)
ds.Lock.Lock()
defer ds.Lock.Unlock()
if ds.closed {
span.Panicf("can not happened. diskId:%v", ds.DiskID)
return
}
span.Infof("== closing diskID:%v ==", ds.DiskID)
if ds.OnClosed != nil {
ds.OnClosed()
}
if ds.closeCh != nil {
close(ds.closeCh)
}
// wait loop in goroutine
go func() {
// wait all loop done
ds.waitAllLoopsStop(ctx)
// clean chunk map
ds.Chunks = make(map[proto.Vuid]core.ChunkAPI)
// clean superblock
sb := ds.SuperBlock
if sb != nil {
sb.Close(ctx)
ds.SuperBlock = nil
}
ds.closed = true
}()
ds.writePool.Close()
ds.readPool.Close()
ds.dataQos.Close()
}
func (ds *DiskStorage) DiskInfo() (info bnapi.DiskInfo) {
ds.Lock.RLock()
defer ds.Lock.RUnlock()
stats := ds.stats.Load().(*core.DiskStats)
// stats
info.Used = stats.Used
info.UsedChunkCnt = int64(len(ds.Chunks))
// for chunk space
info.Free = stats.Free - stats.Reserved
if info.Free < 0 {
info.Free = 0
}
info.Size = stats.TotalDiskSize - stats.Reserved
if info.Size < 0 {
info.Size = 0
}
// config
hostInfo := ds.Conf.HostInfo
info.DiskID = ds.DiskID
info.ClusterID = hostInfo.ClusterID
info.Idc = hostInfo.IDC
info.Rack = hostInfo.Rack
info.Host = hostInfo.Host
info.Path = ds.Conf.Path
// status
info.Status = ds.status
info.CreateAt = time.Unix(0, ds.CreateAt)
info.LastUpdateAt = time.Unix(0, ds.LastUpdateAt)
return
}
func (ds *DiskStorage) Status() (status proto.DiskStatus) {
ds.Lock.RLock()
defer ds.Lock.RUnlock()
return ds.status
}
func (ds *DiskStorage) Stats() (stat core.DiskStats) {
return *(ds.stats.Load().(*core.DiskStats))
}
func (ds *DiskStorage) GetConfig() (config *core.Config) {
return ds.Conf
}
func (ds *DiskStorage) GetIoQos() (ioQos qos.Qos) {
return ds.dataQos
}
func (ds *DiskStorage) GetDataPath() (path string) {
return ds.DataPath
}
func (ds *DiskStorage) GetMetaPath() (path string) {
return ds.MetaPath
}
func (ds *DiskStorage) ID() (id proto.DiskID) {
return ds.DiskID
}
func (ds *DiskStorage) SetStatus(status proto.DiskStatus) {
ds.Lock.Lock()
ds.status = status
ds.Lock.Unlock()
}
func (ds *DiskStorage) isChunksExceeded(ctx context.Context, chunksize int64) bool {
span := trace.SpanFromContextSafe(ctx)
ds.Lock.RLock()
defer ds.Lock.RUnlock()
if len(ds.Chunks) >= int(ds.Conf.MaxChunks) {
return true
}
// unit test skips the following logic
if os.Getenv("JENKINS_TEST") != "" {
return false
}
stats := ds.stats.Load().(*core.DiskStats)
actualTotal := stats.TotalDiskSize - stats.Reserved
if int64(len(ds.Chunks)) >= (actualTotal / chunksize) {
span.Errorf("current:%v, total:%v, chunksize:%v", len(ds.Chunks), actualTotal, chunksize)
return true
}
return false
}
/*
* 1. Create a new chunk
* 2. bind it to vuid
*/
func (dsw *DiskStorageWrapper) CreateChunk(ctx context.Context, vuid proto.Vuid, chunksize int64) (
cs core.ChunkAPI, err error) {
span := trace.SpanFromContextSafe(ctx)
ds := dsw.DiskStorage
if chunksize < 0 || chunksize > MaxChunkSize {
return nil, bloberr.ErrInvalidParam
}
if ds.isChunksExceeded(ctx, chunksize) {
return nil, bloberr.ErrTooManyChunks
}
stats := ds.stats.Load().(*core.DiskStats)
if ds.isMountPoint && stats.Free < chunksize {
return nil, bloberr.ErrDiskNoSpace
}
// The following logic, for the same vuid, only allows serial execution
if ds.ChunkLimitPerKey.Acquire(vuid) != nil {
return nil, bloberr.ErrOverload
}
defer ds.ChunkLimitPerKey.Release(vuid)
ds.Lock.RLock()
_, exist := ds.Chunks[vuid]
ds.Lock.RUnlock()
if exist {
span.Errorf("vuid:%v alread exist.", vuid)
return nil, bloberr.ErrAlreadyExist
}
super := ds.SuperBlock
chunkId := bnapi.NewChunkId(vuid)
nowtime := time.Now().UnixNano()
vm := core.VuidMeta{
Version: _chunkVer[0],
Vuid: vuid,
DiskID: ds.DiskID,
ChunkId: chunkId,
ChunkSize: chunksize,
Ctime: nowtime,
Mtime: nowtime,
Status: bnapi.ChunkStatusNormal,
}
// create chunk storage
cs, err = chunk.NewChunkStorage(ctx, ds.DataPath, vm, dsw.readPool, dsw.writePool, func(option *core.Option) {
option.CreateDataIfMiss = true
option.DB = ds.SuperBlock.db
option.Conf = ds.Conf
option.IoQos = ds.dataQos
option.Disk = dsw
})
if err != nil {
span.Errorf("Failed new chunk:<%s>, err:%v", ds.DataPath, err)
return nil, err
}
// save to superBlock
err = super.UpsertChunk(ctx, vm.ChunkId, vm)
if err != nil {
span.Errorf("Failed upsert chunk<%s>, err:%v", vm.ChunkId, err)
return nil, err
}
// update bind it to vuid
err = super.BindVuidChunk(ctx, vuid, chunkId)
if err != nil {
span.Errorf("Failed vuid<%d>, chunkid<%s>, err:%v", vuid, chunkId, err)
return nil, err
}
// add to map
ds.Lock.Lock()
ds.Chunks[vuid] = cs
ds.Lock.Unlock()
return cs, nil
}
func (ds *DiskStorage) loopAttach(f func()) {
ds.wg.Add(1)
go func() {
defer ds.wg.Done()
f()
}()
}
// parse disk, make disk storage
func newDiskStorage(ctx context.Context, conf core.Config) (ds *DiskStorage, err error) {
span, _ := trace.StartSpanFromContextWithTraceID(context.Background(), "", conf.Path)
// init config
err = core.InitConfig(&conf)
if err != nil {
return nil, err
}
span.Infof("config:%v", conf)
path, err := filepath.Abs(conf.Path)
if err != nil {
return nil, err
}
metaRoot := conf.MetaRootPrefix
if metaRoot != "" {
if exist, err := bncom.IsFileExists(metaRoot); err != nil || !exist {
span.Errorf("meta path: %s not exist ( occur err:%v ), exit", metaRoot, err)
return nil, errors.New("meta root prefix not exist")
}
}
if conf.MustMountPoint {
if !myos.IsMountPoint(conf.Path) {
span.Errorf("%s must mount point.", conf.Path)
return nil, errors.New("must mount point")
}
if metaRoot != "" && !myos.IsMountPoint(metaRoot) {
span.Errorf("%s must mount point.", metaRoot)
return nil, errors.New("must mount point")
}
}
if conf.AutoFormat {
span.Warnf("auto format mode, will ensure directory.")
err = core.EnsureDiskArea(path, metaRoot)
if err != nil {
return nil, err
}
}
diskDataPath := core.GetDataPath(path)
diskMetaPath := core.GetMetaPath(path, metaRoot)
span.Infof("datapath: %v, metapath:%v", diskDataPath, diskMetaPath)
// load superblock๏ผŒcreate or open
sb, err := NewSuperBlock(diskMetaPath, &conf)
if err != nil {
return nil, err
}
var dm core.DiskMeta
dm, err = sb.LoadDiskInfo(ctx)
if err != nil {
if os.IsNotExist(err) {
span.Warnf("disk not format. will format and register")
} else {
return nil, err
}
dm = core.DiskMeta{}
}
if !dm.Registered {
exist, err := core.IsFormatConfigExist(path)
if err != nil || exist {
span.Errorf("unexpected error. format file ( in %s ) should not exist, but, exist:%v err:%v", path, exist, err)
return nil, bloberr.ErrUnexpected
}
dm, err = registerDisk(ctx, sb, &conf)
if err != nil {
span.Errorf("register disk failed: %v", err)
return nil, err
}
}
span.Infof("diskID:%d", dm.DiskID)
// check format info
formatInfo, err := core.ReadFormatInfo(ctx, path)
if err != nil {
span.Errorf("Failed read format info, err:%v", err)
return nil, err
}
if formatInfo.DiskID != dm.DiskID {
span.Errorf("unexpected error. diskId not match. format:%v, dm:%v", formatInfo, dm)
return nil, bloberr.ErrUnexpected
}
// init eio handler
sb.SetHandlerIOError(func(err error) {
conf.HandleIOError(context.Background(), dm.DiskID, err)
})
// io visualization: init data io stat
dataIos, err := flow.NewIOFlowStat(dm.DiskID.ToString(), conf.IOStatFileDryRun)
if err != nil {
span.Errorf("Failed new dataio flow stat, err:%v", err)
return nil, err
}
diskView := flow.NewDiskViewer(dataIos)
// init Qos Manager
conf.DataQos.StatGetter = dataIos
conf.DataQos.DiskViewer = diskView
dataQos, err := qos.NewIoQueueQos(conf.DataQos)
if err != nil {
span.Errorf("Failed new io qos, err:%v", err)
return nil, err
}
// setting io pools
writePool := taskpool.NewWritePool(conf.WriteThreadCnt, conf.WriteQueueDepth)
readPool := taskpool.NewReadPool(conf.ReadThreadCnt, conf.ReadQueueDepth)
ds = &DiskStorage{
DiskID: dm.DiskID,
SuperBlock: sb,
DataPath: diskDataPath,
MetaPath: diskMetaPath,
ChunkLimitPerKey: keycount.NewBlockingKeyCountLimit(1),
Conf: &conf,
closeCh: make(chan struct{}),
compactCh: make(chan proto.Vuid),
ctx: ctx,
status: dm.Status,
isMountPoint: myos.IsMountPoint(conf.Path),
dataQos: dataQos,
CreateAt: dm.Ctime,
LastUpdateAt: dm.Mtime,
writePool: writePool,
readPool: readPool,
}
if err = ds.fillDiskUsage(ctx); err != nil {
span.Errorf("Failed fill disk usage, err:%v", err)
return nil, err
}
// background loop
ds.loopAttach(ds.loopCleanChunk)
ds.loopAttach(ds.loopCompactFile)
ds.loopAttach(ds.loopDiskUsage)
ds.loopAttach(ds.loopCleanTrash)
ds.loopAttach(ds.loopMetricReport)
return ds, nil
}
func NewDiskStorage(ctx context.Context, conf core.Config) (dsw *DiskStorageWrapper, err error) {
ds, err := newDiskStorage(ctx, conf)
if err != nil {
return nil, err
}
dsw = &DiskStorageWrapper{DiskStorage: ds}
err = dsw.RestoreChunkStorage(ctx)
if err != nil {
return nil, err
}
// It will be automatically recycled when gc
runtime.SetFinalizer(dsw, func(wapper *DiskStorageWrapper) {
wapper.Close(context.Background())
})
return dsw, nil
}
func registerDisk(ctx context.Context, sb *SuperBlock, conf *core.Config) (dm core.DiskMeta, err error) {
span := trace.SpanFromContextSafe(ctx)
span.Infof("disk conf:<%v> auto format", conf)
// allocate global Uniq diskID
diskID, err := conf.AllocDiskID(ctx)
if err != nil {
span.Errorf("Failed alloc diskId: %d, err:%v", dm.DiskID, err)
return
}
span.Debugf("diskId: <%v>", diskID)
now := time.Now().UnixNano()
format := &core.FormatInfo{
FormatInfoProtectedField: core.FormatInfoProtectedField{
DiskID: diskID,
Version: _diskVer[0],
Format: core.FormatMetaTypeV1,
Ctime: now,
},
}
checkSum, err := format.CalCheckSum()
if err != nil {
span.Errorf("cal format info crc failed: %v", err)
return
}
format.CheckSum = checkSum
// dm.Host =
dm = core.DiskMeta{
FormatInfo: *format,
Mtime: now,
Registered: true,
Status: proto.DiskStatusNormal,
Path: conf.Path,
}
err = sb.UpsertDisk(ctx, dm.DiskID, dm)
if err != nil {
span.Errorf("Failed upsert disk: %d, err:%v", dm.DiskID, err)
return
}
err = core.SaveDiskFormatInfo(ctx, conf.Path, format)
if err != nil {
span.Errorf("Failed save disk[%s] format info, err:%v", conf.Path, err)
return
}
span.Infof("register disk(%v) success", diskID)
return
}
func (dsw *DiskStorageWrapper) RestoreChunkStorage(ctx context.Context) (err error) {
span := trace.SpanFromContextSafe(ctx)
ds := dsw.DiskStorage
sb := ds.SuperBlock
// load chunkmeta
vuidMaps, err := sb.ListVuids(ctx)
if err != nil {
span.Errorf("Failed list chunks: %v", err)
return err
}
vuidMetas, err := sb.ListChunks(ctx)
if err != nil {
span.Errorf("Failed list chunks: %v", err)
return err
}
chunks := make(map[proto.Vuid]core.ChunkAPI)
for vuid, chunkid := range vuidMaps {
span.Debugf("vuid:%d, chunkid: %s", vuid, chunkid)
vm := vuidMetas[chunkid]
if vm.Status == bnapi.ChunkStatusRelease {
span.Warnf("vuid:%d(chunk:%s) status is release", vm.Vuid, vm.ChunkId)
continue
}
if vm.Compacting {
vm.Compacting = false
err := sb.UpsertChunk(ctx, chunkid, vm)
if err != nil {
span.Errorf("Failed upsert chunk compacting, chunkid:%s, vm:%v", chunkid, vm)
return err
}
err = ds.notifyCompacting(ctx, vuid, false)
if err != nil {
span.Errorf("set chunk(%v) compacting false failed: %v", vuid, err)
return err
}
}
cs, err := chunk.NewChunkStorage(ctx, ds.DataPath, vm, ds.readPool, ds.writePool, func(o *core.Option) {
o.Conf = ds.Conf
o.DB = sb.db
o.Disk = dsw
o.IoQos = ds.dataQos
o.CreateDataIfMiss = false
})
if err != nil {
span.Errorf("Failed New chunk, path:%s, vm:%v", ds.DataPath, vm)
return err
}
chunks[vm.Vuid] = cs
}
ds.Lock.Lock()
ds.Chunks = chunks
ds.Lock.Unlock()
span.Debugf("build ChunkStorage success")
return
}
func (ds *DiskStorage) ResetChunks(ctx context.Context) {
ds.Lock.Lock()
defer ds.Lock.Unlock()
ds.Chunks = make(map[proto.Vuid]core.ChunkAPI)
}
func (ds *DiskStorage) ReleaseChunk(ctx context.Context, vuid proto.Vuid, force bool) (err error) {
span := trace.SpanFromContextSafe(ctx)
// The following logic, for the same vuid, only allows serial execution
if ds.ChunkLimitPerKey.Acquire(vuid) != nil {
return bloberr.ErrOverload
}
defer ds.ChunkLimitPerKey.Release(vuid)
if ds.status >= proto.DiskStatusBroken {
return bloberr.ErrDiskBroken
}
ds.Lock.RLock()
cs, exist := ds.Chunks[vuid]
ds.Lock.RUnlock()
if !exist {
span.Errorf("vuid:%v not exist in ds.Chunks", vuid)
return bloberr.ErrNoSuchVuid
}
// can not convert status
if !force && !isValidStateTransition(cs.Status(), bnapi.ChunkStatusRelease) {
span.Errorf("can not release chunk(%s) status:%v", cs.ID(), cs.Status())
return bloberr.ErrUnexpected
}
if cs.HasPendingRequest() {
span.Errorf("can not happen. chunk:%s has pending reqs", cs.ID())
return bloberr.ErrChunkInuse
}
span.Warnf("will mark vuid(%v)/chunk(%s) destroy. force mode(%v)", vuid, cs.ID(), force)
// delete node from map
ds.Lock.Lock()
delete(ds.Chunks, vuid)
ds.Lock.Unlock()
// unbind vuid
err = ds.SuperBlock.UnbindVuidChunk(ctx, vuid, cs.ID())
if err != nil {
span.Errorf("Failed unbind vuid:%d chunk:%s", vuid, cs.ID())
return err
}
// update chunk meta
vm := cs.VuidMeta()
vm.Status = bnapi.ChunkStatusRelease
vm.Reason = bnapi.ReleaseForUser
vm.Mtime = time.Now().UnixNano()
err = ds.SuperBlock.UpsertChunk(ctx, cs.ID(), *vm)
if err != nil {
span.Errorf("update chunk(%s) status to release failed: %v", vm.ChunkId, err)
return err
}
// update ChunkStorage status in memory
cs.SetStatus(bnapi.ChunkStatusRelease)
cs = nil
span.Infof("release chunk<%s> success", vm.ChunkId)
return nil
}
/*
* chunk status changing must call this method
* first: change persistence status
* second: change status in memory
* concurrency safety: only allows serial execution for the same vuid
*/
func (ds *DiskStorage) UpdateChunkStatus(ctx context.Context, vuid proto.Vuid, status bnapi.ChunkStatus) (err error) {
span := trace.SpanFromContextSafe(ctx)
if !bnapi.IsValidChunkStatus(status) {
span.Errorf("chunk status is invalid: %v", status)
return bloberr.ErrInvalidParam
}
// The following logic, for the same vuid, only allows serial execution
if ds.ChunkLimitPerKey.Acquire(vuid) != nil {
return bloberr.ErrOverload
}
defer ds.ChunkLimitPerKey.Release(vuid)
ds.Lock.RLock()
cs, exist := ds.Chunks[vuid]
ds.Lock.RUnlock()
if !exist {
// superBlock can read such vuid meta, but does not exist in disk.Chunks
// such vuid have been released
span.Errorf("disk(%v) no such vuid(%v)", ds.DiskID, vuid)
return bloberr.ErrNoSuchVuid
}
vm := cs.VuidMeta()
if vm.Status == status {
span.Debugf("chunk status is same")
return nil
}
// can not convert status
if !isValidStateTransition(vm.Status, status) {
span.Errorf("can not convert chunk(%s) status:%v to %v", cs.ID(), vm.Status, status)
return bloberr.ErrUnexpected
}
vm.Status = status
vm.Mtime = time.Now().UnixNano()
err = ds.SuperBlock.UpsertChunk(ctx, cs.ID(), *vm)
if err != nil {
span.Errorf("update chunk(%s) status to %v failed: %v", vm.ChunkId, status, err)
return err
}
// update ChunkStorage status in memory
cs.SetStatus(status)
return nil
}
func (ds *DiskStorage) UpdateChunkCompactState(ctx context.Context, vuid proto.Vuid, compacting bool) (
err error,
) {
span := trace.SpanFromContextSafe(ctx)
span.Debugf("update vuid:%v compacting:%v", vuid, compacting)
// The following logic, for the same vuid, only allows serial execution
if ds.ChunkLimitPerKey.Acquire(vuid) != nil {
return bloberr.ErrOverload
}
defer ds.ChunkLimitPerKey.Release(vuid)
ds.Lock.RLock()
cs, exist := ds.Chunks[vuid]
ds.Lock.RUnlock()
if !exist {
// superBlock can read such vuid meta, but does not exist in disk.Chunks
// such vuid have been released
span.Errorf("disk(%v) no such vuid(%v)", ds.DiskID, vuid)
return bloberr.ErrNoSuchVuid
}
vm := cs.VuidMeta()
vm.Compacting = compacting
vm.Mtime = time.Now().UnixNano()
err = ds.SuperBlock.UpsertChunk(ctx, cs.ID(), *vm)
if err != nil {
span.Errorf("update chunk(%s) status to %v failed: %v",
vm.ChunkId, compacting, err)
return err
}
return nil
}
func (ds *DiskStorage) UpdateDiskStatus(ctx context.Context, status proto.DiskStatus) (err error) {
span := trace.SpanFromContextSafe(ctx)
if status >= proto.DiskStatusMax {
return bloberr.ErrInvalidParam
}
// read disk meta
di, err := ds.SuperBlock.LoadDiskInfo(ctx)
if err != nil {
return
}
// persistence disk status
di.Status = status
err = ds.SuperBlock.UpsertDisk(ctx, ds.DiskID, di)
if err != nil {
span.Errorf("update disk(%v) persistence status failed: %v", ds.DiskID, err)
return err
}
// modify
di.Status = status
di.Mtime = time.Now().UnixNano()
// disk status in memory
ds.Lock.Lock()
ds.status = status
ds.Lock.Unlock()
return
}
func (ds *DiskStorage) ListChunks(ctx context.Context) (chunks []core.VuidMeta, err error) {
chunksmap, err := ds.SuperBlock.ListChunks(ctx)
if err != nil {
return nil, err
}
chunks = []core.VuidMeta{}
for _, chunk := range chunksmap {
chunks = append(chunks, chunk)
}
return chunks, nil
}
func (ds *DiskStorage) LoadDiskInfo(ctx context.Context) (dm core.DiskMeta, err error) {
return ds.SuperBlock.LoadDiskInfo(ctx)
}
func (ds *DiskStorage) loopCleanChunk() {
span, _ := trace.StartSpanFromContextWithTraceID(context.Background(), "", "CleanChunk"+ds.Conf.Path)
span.Infof("loop clean chunk start")
timer := initTimer(ds.Conf.ChunkCleanIntervalSec)
defer timer.Stop()
for {
select {
case <-ds.closeCh:
span.Infof("loop clean chunk done")
return
case <-timer.C:
if err := ds.cleanReleasedChunks(); err != nil {
span.Errorf("Failed exec Cleanchunks. err:%v", err)
}
resetTimer(ds.Conf.ChunkCleanIntervalSec, timer)
}
}
}
func (ds *DiskStorage) sceneWithoutProtection(ctx context.Context, meta core.VuidMeta) bool {
span := trace.SpanFromContextSafe(ctx)
if meta.Reason != bnapi.ReleaseForCompact {
return false
}
span.Debugf("id:%s meta:%v without protection", meta.ChunkId, meta)
return true
}
func (ds *DiskStorage) cleanReleasedChunks() (err error) {
span, ctx := trace.StartSpanFromContextWithTraceID(context.Background(), "", bncom.BackgroudReqID("CleanChunk"+ds.Conf.Path))
span.Debugf("come in CleanChunks.")
// set io type
ctx = bnapi.SetIoType(ctx, bnapi.BackgroundIO)
protectionPeriod := time.Duration(ds.Conf.ChunkReleaseProtectionM)
now := time.Now().UnixNano()
chunks, err := ds.ListChunks(ctx)
if err != nil {
span.Errorf("%v list chunks failed: %v", ds.MetaPath, err)
return
}
for _, ck := range chunks {
if ck.Status != bnapi.ChunkStatusRelease {
continue
}
if !ds.sceneWithoutProtection(ctx, ck) && now-ck.Mtime < int64(time.Minute*protectionPeriod) {
span.Debugf("%s still in protection period", ck.ChunkId)
continue
}
chunkid, err := ds.SuperBlock.ReadVuidBind(ctx, ck.Vuid)
if err == nil && chunkid == ck.ChunkId {
span.Warnf("can not happen. vuid:%d bind %s. skip", ck.Vuid, ck.ChunkId)
continue
}
if err = ds.realCleanChunk(ctx, ck.ChunkId); err != nil {
span.Errorf("failed clean chunk:%s, err:%v", ck.ChunkId, err)
continue
}
}
return
}
func (ds *DiskStorage) realCleanChunk(ctx context.Context, id bnapi.ChunkId) (err error) {
span := trace.SpanFromContextSafe(ctx)
span.Warnf("will clean chunk:(%s)", id)
err = ds.cleanChunk(ctx, id, false)
if err != nil {
span.Errorf("Failed cleanChunk:%s err:%v", id, err)
return
}
// delete this chunk's meta
err = ds.SuperBlock.DeleteChunk(ctx, id)
if err != nil {
span.Errorf("Failed Delete Chunk:%s err:%v", id, err)
return
}
span.Infof("disk(%v) clean chunk(%v) success", ds.DiskID, id)
return nil
}
// Clean up the space of a chunk, including metadata and data
// NOTE: Maybe a long time
func (ds *DiskStorage) cleanChunk(ctx context.Context, id bnapi.ChunkId, toTrash bool) (err error) {
span := trace.SpanFromContextSafe(ctx)
// clean meta
span.Debugf("clean %s chunk meta begin ===", id)
defer span.Debugf("clean %s chunk data end ===", id)
err = ds.SuperBlock.CleanChunkSpace(ctx, id)
if err != nil {
span.Errorf("clean %s chunk meta failed: %s", id, err)
return
}
span.Debugf("clean %s chunk meta end ===", id)
// clean data
span.Debugf("clean %s chunk data begin ===", id)
chunkDataFile := filepath.Join(ds.DataPath, id.String())
if !toTrash {
return os.Remove(chunkDataFile)
} else {
return ds.moveToTrash(ctx, chunkDataFile)
}
}
func (ds *DiskStorage) GetChunkStorage(vuid proto.Vuid) (cs core.ChunkAPI, found bool) {
ds.Lock.RLock()
defer ds.Lock.RUnlock()
cs, ok := ds.Chunks[vuid]
if !ok {
return nil, false
}
return cs, true
}
// get disk usage
func (ds *DiskStorage) loopDiskUsage() {
span, ctx := trace.StartSpanFromContextWithTraceID(context.Background(), "", "DiskUsage"+ds.Conf.Path)
span.Infof("loop disk usage start")
timer := initTimer(ds.Conf.DiskUsageIntervalSec)
defer timer.Stop()
for {
select {
case <-ds.closeCh:
span.Infof("loop disk usage done")
return
case <-timer.C:
if err := ds.fillDiskUsage(ctx); err != nil {
span.Errorf("Failed exec disk usage. err:%v", err)
}
resetTimer(ds.Conf.DiskUsageIntervalSec, timer)
}
}
}
func (ds *DiskStorage) fillDiskUsage(ctx context.Context) (err error) {
span := trace.SpanFromContextSafe(ctx)
span.Debugf("will fill disk usage")
// load disk info
rootInfo, err := myos.GetDiskInfo(ds.Conf.Path)
if err != nil {
span.Errorf("Failed get [%s] info, err:%v", ds.Conf.Path, err)
return err
}
stats := new(core.DiskStats)
stats.Reserved = ds.Conf.DiskReservedSpaceB
stats.Used = int64(rootInfo.Total - rootInfo.Free)
stats.Free = int64(rootInfo.Free)
stats.TotalDiskSize = int64(rootInfo.Total)
ds.stats.Store(stats)
return nil
}
func (ds *DiskStorage) WalkChunksWithLock(ctx context.Context, walkFn func(cs core.ChunkAPI) error) (err error) {
ds.Lock.RLock()
defer ds.Lock.RUnlock()
for _, cs := range ds.Chunks {
if err = walkFn(cs); err != nil {
return err
}
}
return nil
}
func isValidStateTransition(src, dest bnapi.ChunkStatus) bool {
validStates, exist := StateTransitionRules[src]
if !exist {
return false
}
for _, s := range validStates {
if s == dest {
return true
}
}
return false
}
func initTimer(ts int64) *time.Timer {
return time.NewTimer(time.Duration(ts+rand.Int63n(RandomIntervalS)) * time.Second)
}
func resetTimer(ts int64, timer *time.Timer) {
rand.Seed(time.Now().UnixNano())
timer.Reset(time.Duration(ts+rand.Int63n(RandomIntervalS)) * time.Second)
}