cubefs/lcnode/snapshot_scanner.go
zhaochenyang a6d125f5ce feat(lcnode/master): lcnode support bucket lifecycle and vol snapshot delete
Signed-off-by: zhaochenyang <zhaochenyang@oppo.com>
2023-08-30 20:05:32 +08:00

494 lines
16 KiB
Go

// Copyright 2023 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 lcnode
import (
"os"
"sync/atomic"
"syscall"
"time"
"github.com/cubefs/cubefs/proto"
"github.com/cubefs/cubefs/sdk/meta"
"github.com/cubefs/cubefs/util/log"
"github.com/cubefs/cubefs/util/routinepool"
"github.com/cubefs/cubefs/util/unboundedchan"
)
const (
SnapScanTypeAll int = 0
SnapScanTypeOnlyFile int = 1
SnapScanTypeOnlyDepth int = 2
SnapScanTypeOnlyDir int = 3
SnapScanTypeOnlyDirAndDepth int = 4
)
type SnapshotScanner struct {
ID string
Volume string
mw *meta.MetaWrapper
lcnode *LcNode
adminTask *proto.AdminTask
inodeChan *unboundedchan.UnboundedChan
rPoll *routinepool.RoutinePool
currentStat proto.SnapshotStatistics
scanFunc func()
scanType int
stopC chan bool
}
func NewSnapshotScanner(adminTask *proto.AdminTask, l *LcNode) (*SnapshotScanner, error) {
request := adminTask.Request.(*proto.SnapshotVerDelTaskRequest)
var err error
var metaConfig = &meta.MetaConfig{
Volume: request.Task.VolName,
Masters: l.masters,
Authenticate: false,
ValidateOwner: false,
}
var metaWrapper *meta.MetaWrapper
if metaWrapper, err = meta.NewMetaWrapper(metaConfig); err != nil {
return nil, err
}
scanner := &SnapshotScanner{
ID: request.Task.Key(),
Volume: request.Task.VolName,
mw: metaWrapper,
lcnode: l,
adminTask: adminTask,
inodeChan: unboundedchan.NewUnboundedChan(defaultUnboundedChanInitCapacity),
rPoll: routinepool.NewRoutinePool(snapshotRoutineNumPerTask),
currentStat: proto.SnapshotStatistics{},
scanType: SnapScanTypeAll,
stopC: make(chan bool, 0),
}
return scanner, nil
}
func (l *LcNode) startSnapshotScan(adminTask *proto.AdminTask) (err error) {
request := adminTask.Request.(*proto.SnapshotVerDelTaskRequest)
log.LogInfof("startSnapshotScan: scan task(%v) received!", request.Task)
response := &proto.SnapshotVerDelTaskResponse{}
adminTask.Response = response
defer func() {
if err != nil {
l.respondToMaster(adminTask)
}
}()
l.scannerMutex.Lock()
if _, ok := l.snapshotScanners[request.Task.Key()]; ok {
log.LogInfof("startSnapshotScan: scan task(%v) is already running!", request.Task)
l.scannerMutex.Unlock()
return
}
var scanner *SnapshotScanner
scanner, err = NewSnapshotScanner(adminTask, l)
if err != nil {
log.LogErrorf("startSnapshotScan: NewSnapshotScanner err(%v)", err)
response.Status = proto.TaskFailed
response.Result = err.Error()
l.scannerMutex.Unlock()
return
}
l.snapshotScanners[request.Task.Key()] = scanner
l.scannerMutex.Unlock()
if err = scanner.Start(); err != nil {
return
}
return
}
func (s *SnapshotScanner) getTaskVolName() string {
request := s.adminTask.Request.(*proto.SnapshotVerDelTaskRequest)
return request.Task.VolName
}
func (s *SnapshotScanner) getTaskVerSeq() uint64 {
request := s.adminTask.Request.(*proto.SnapshotVerDelTaskRequest)
return request.Task.VerSeq
}
func (s *SnapshotScanner) Stop() {
close(s.stopC)
s.rPoll.WaitAndClose()
close(s.inodeChan.In)
log.LogDebugf("snapshot scanner(%v) stopped", s.ID)
}
func (s *SnapshotScanner) Start() (err error) {
//1. first delete all files
if err = s.startScan(SnapScanTypeOnlyFile, true, false); err != nil {
return err
}
log.LogDebugf("startScan: first round files done!")
//2. depth first delete all dirs
if err = s.startScan(SnapScanTypeOnlyDirAndDepth, false, true); err != nil {
return err
}
log.LogDebugf("startScan: second round dirs done!")
return
}
func (s *SnapshotScanner) startScan(scanType int, syncWait bool, report bool) (err error) {
response := s.adminTask.Response.(*proto.SnapshotVerDelTaskResponse)
s.scanType = scanType
if s.scanFunc == nil {
log.LogDebugf("startScan: start scanFunc")
s.scanFunc = s.scan
go s.scanFunc()
}
prefixDentry := &proto.ScanDentry{
Inode: proto.RootIno,
Type: proto.Mode(os.ModeDir),
}
t := time.Now()
response.StartTime = &t
log.LogDebugf("startScan: scan type(%v), first dir entry(%v) in!", scanType, prefixDentry)
s.inodeChan.In <- prefixDentry
if syncWait {
s.checkScanning(report)
} else {
go s.checkScanning(report)
}
return
}
func (s *SnapshotScanner) getDirJob(dentry *proto.ScanDentry) (job func()) {
//no need to lock
if s.scanType == SnapScanTypeOnlyDepth || s.scanType == SnapScanTypeOnlyDirAndDepth {
log.LogDebugf("getDirJob: Only depth first job ")
job = func() {
s.handlVerDelDepthFirst(dentry)
}
return
}
if s.inodeChan.Len() > maxDirChanNum {
log.LogDebugf("getDirJob: Depth first job")
job = func() {
s.handlVerDelDepthFirst(dentry)
}
} else {
log.LogDebugf("getDirJob: Breadth first job")
job = func() {
s.handlVerDel(dentry)
}
}
return
}
func (s *SnapshotScanner) scan() {
log.LogDebugf("Enter scan")
defer func() {
log.LogDebugf("Exit scan")
}()
for {
select {
case <-s.stopC:
return
case val, ok := <-s.inodeChan.Out:
if !ok {
log.LogWarnf("inodeChan closed")
} else {
dentry := val.(*proto.ScanDentry)
job := s.getDirJob(dentry)
_, err := s.rPoll.Submit(job)
if err != nil {
log.LogErrorf("handlVerDel failed, err(%v)", err)
}
}
}
}
}
func (s *SnapshotScanner) handlVerDelDepthFirst(dentry *proto.ScanDentry) {
var (
children []proto.Dentry
ino *proto.InodeInfo
err error
)
if os.FileMode(dentry.Type).IsDir() {
marker := ""
done := false
onlyDir := s.scanType == SnapScanTypeOnlyDir || s.scanType == SnapScanTypeOnlyDirAndDepth
for !done {
children, err = s.mw.ReadDirLimitByVer(dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), onlyDir)
if err != nil && err != syscall.ENOENT {
log.LogErrorf("action[handlVerDelDepthFirst] ReadDirLimitByVer failed, parent[%v] maker[%v] limit[%v] verSeq[%v] err[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), err)
return
}
log.LogDebugf("action[handlVerDelDepthFirst] ReadDirLimitByVer parent[%v] maker[%v] limit[%v] verSeq[%v] children[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), len(children))
if err == syscall.ENOENT {
done = true
log.LogErrorf("action[handlVerDelDepthFirst] ReadDirLimitByVer failed, parent[%v] maker[%v] limit[%v] verSeq[%v] err[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), err)
break
}
if marker != "" {
if len(children) >= 1 && marker == children[0].Name {
if len(children) <= 1 {
done = true
log.LogDebugf("action[handlVerDelDepthFirst] ReadDirLimit_ll done, parent[%v] maker[%v] limit[%v] verSeq[%v] children[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), children)
break
} else {
skippedChild := children[0]
children = children[1:]
log.LogDebugf("action[handlVerDelDepthFirst] ReadDirLimit_ll skip last marker[%v], parent[%v] verSeq[%v] skippedName[%v]",
marker, dentry.Inode, s.getTaskVerSeq(), skippedChild.Name)
}
}
}
files := make([]*proto.ScanDentry, 0)
dirs := make([]*proto.ScanDentry, 0)
for _, child := range children {
childDentry := &proto.ScanDentry{
ParentId: dentry.Inode,
Name: child.Name,
Inode: child.Inode,
Type: child.Type,
}
if os.FileMode(childDentry.Type).IsDir() {
dirs = append(dirs, childDentry)
} else {
files = append(files, childDentry)
}
}
for _, file := range files {
if ino, err = s.mw.Delete_Ver_ll(file.ParentId, file.Name, false, s.getTaskVerSeq()); err != nil {
log.LogErrorf("action[handlVerDelDepthFirst] Delete_Ver_ll failed, file(parent[%v] child name[%v]) verSeq[%v] err[%v]",
file.ParentId, file.Name, s.getTaskVerSeq(), err)
atomic.AddInt64(&s.currentStat.ErrorSkippedNum, 1)
} else {
log.LogDebugf("action[handlVerDelDepthFirst] Delete_Ver_ll success, file(parent[%v] child name[%v]) verSeq[%v] ino[%v]",
file.ParentId, file.Name, s.getTaskVerSeq(), ino)
atomic.AddInt64(&s.currentStat.FileNum, 1)
atomic.AddInt64(&s.currentStat.TotalInodeNum, 1)
}
}
for _, dir := range dirs {
s.handlVerDelDepthFirst(dir)
}
childrenNr := len(children)
if (marker == "" && childrenNr < snapshotRoutineNumPerTask) || (marker != "" && childrenNr+1 < snapshotRoutineNumPerTask) {
log.LogDebugf("action[handlVerDelDepthFirst] ReadDirLimit_ll done, parent[%v]",
dentry.Inode)
done = true
} else {
marker = children[childrenNr-1].Name
log.LogDebugf("action[handlVerDelDepthFirst] ReadDirLimit_ll next marker[%v] parent[%v]", marker, dentry.Inode)
}
}
}
if s.scanType != SnapScanTypeOnlyFile {
if ino, err = s.mw.Delete_Ver_ll(dentry.ParentId, dentry.Name, false, s.getTaskVerSeq()); err != nil {
if dentry.ParentId >= 1 {
log.LogErrorf("action[handlVerDelDepthFirst] Delete_Ver_ll failed, dir(parent[%v] child name[%v]) verSeq[%v] err[%v]",
dentry.ParentId, dentry.Name, s.getTaskVerSeq(), err)
atomic.AddInt64(&s.currentStat.ErrorSkippedNum, 1)
}
} else {
log.LogDebugf("action[handlVerDelDepthFirst] Delete_Ver_ll success, dir(parent[%v] child name[%v]) verSeq[%v] ino[%v]",
dentry.ParentId, dentry.Name, s.getTaskVerSeq(), ino)
atomic.AddInt64(&s.currentStat.DirNum, 1)
atomic.AddInt64(&s.currentStat.TotalInodeNum, 1)
}
}
}
func (s *SnapshotScanner) handlVerDel(dentry *proto.ScanDentry) {
var (
children []proto.Dentry
ino *proto.InodeInfo
err error
)
scanDentries := make([]*proto.ScanDentry, 0)
if os.FileMode(dentry.Type).IsDir() {
totalChildDirNum := 0
totalChildFileNum := 0
marker := ""
done := false
onlyDir := s.scanType == SnapScanTypeOnlyDir || s.scanType == SnapScanTypeOnlyDirAndDepth
for !done {
children, err = s.mw.ReadDirLimitByVer(dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), onlyDir)
if err != nil && err != syscall.ENOENT {
log.LogErrorf("action[handlVerDel] ReadDirLimitByVer failed, parent[%v] maker[%v] limit[%v] verSeq[%v] err[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), err)
return
}
log.LogDebugf("action[handlVerDel] ReadDirLimitByVer parent[%v] maker[%v] limit[%v] verSeq[%v] children[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), len(children))
if err == syscall.ENOENT {
done = true
log.LogErrorf("action[handlVerDel] ReadDirLimitByVer failed, parent[%v] maker[%v] limit[%v] verSeq[%v] err[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), err)
break
}
if marker != "" {
if len(children) >= 1 && marker == children[0].Name {
if len(children) <= 1 {
done = true
log.LogDebugf("action[handlVerDel] ReadDirLimit_ll done, parent[%v] maker[%v] limit[%v] verSeq[%v] children[%v]",
dentry.Inode, marker, uint64(snapshotRoutineNumPerTask), s.getTaskVerSeq(), children)
break
} else {
skippedChild := children[0]
children = children[1:]
log.LogDebugf("action[handlVerDel] ReadDirLimit_ll skip last marker[%v], parent[%v] verSeq[%v] skippedName[%v]",
marker, dentry.Inode, s.getTaskVerSeq(), skippedChild.Name)
}
}
}
for _, child := range children {
childDentry := &proto.ScanDentry{
ParentId: dentry.Inode,
Name: child.Name,
Inode: child.Inode,
Type: child.Type,
}
if os.FileMode(childDentry.Type).IsDir() {
s.inodeChan.In <- childDentry
totalChildDirNum++
log.LogDebugf("action[handlVerDel] push dir(parent[%v] child name[%v] ino[%v]) in channel",
childDentry.ParentId, childDentry.Name, childDentry.Inode)
} else {
scanDentries = append(scanDentries, childDentry)
}
}
for _, file := range scanDentries {
if ino, err = s.mw.Delete_Ver_ll(file.ParentId, file.Name, false, s.getTaskVerSeq()); err != nil {
log.LogErrorf("action[handlVerDel] Delete_Ver_ll failed, file(parent[%v] child name[%v]) verSeq[%v] err[%v]",
file.ParentId, file.Name, s.getTaskVerSeq(), err)
atomic.AddInt64(&s.currentStat.ErrorSkippedNum, 1)
} else {
totalChildFileNum++
log.LogDebugf("action[handlVerDel] Delete_Ver_ll success, file(parent[%v] child name[%v]) verSeq[%v] ino[%v]",
file.ParentId, file.Name, s.getTaskVerSeq(), ino)
atomic.AddInt64(&s.currentStat.FileNum, 1)
atomic.AddInt64(&s.currentStat.TotalInodeNum, 1)
}
}
scanDentries = scanDentries[:0]
childrenNr := len(children)
if (marker == "" && childrenNr < snapshotRoutineNumPerTask) || (marker != "" && childrenNr+1 < snapshotRoutineNumPerTask) {
log.LogDebugf("action[handlVerDel] ReadDirLimit_ll done, parent[%v] total childrenNr[%v] marker[%v]",
dentry.Inode, totalChildFileNum+totalChildDirNum, marker)
done = true
} else {
marker = children[childrenNr-1].Name
log.LogDebugf("action[handlVerDel] ReadDirLimit_ll next marker[%v] parent[%v]", marker, dentry.Inode)
}
}
}
if s.scanType != SnapScanTypeOnlyFile {
if ino, err = s.mw.Delete_Ver_ll(dentry.ParentId, dentry.Name, os.FileMode(dentry.Type).IsDir(), s.getTaskVerSeq()); err != nil {
if dentry.ParentId >= 1 {
log.LogErrorf("action[handlVerDel] Delete_Ver_ll failed, dir(parent[%v] child name[%v]) verSeq[%v] err[%v]",
dentry.ParentId, dentry.Name, s.getTaskVerSeq(), err)
atomic.AddInt64(&s.currentStat.ErrorSkippedNum, 1)
}
} else {
log.LogDebugf("action[handlVerDel] Delete_Ver_ll success, dir(parent[%v] child name[%v]) verSeq[%v] ino[%v]",
dentry.ParentId, dentry.Name, s.getTaskVerSeq(), ino)
atomic.AddInt64(&s.currentStat.DirNum, 1)
atomic.AddInt64(&s.currentStat.TotalInodeNum, 1)
}
}
}
func (s *SnapshotScanner) DoneScanning() bool {
log.LogDebugf("inodeChan.Len(%v) rPoll.RunningNum(%v)", s.inodeChan.Len(), s.rPoll.RunningNum())
return s.inodeChan.Len() == 0 && s.rPoll.RunningNum() == 0
}
func (s *SnapshotScanner) checkScanning(report bool) {
dur := time.Second * time.Duration(scanCheckInterval)
taskCheckTimer := time.NewTimer(dur)
for {
select {
case <-s.stopC:
log.LogDebugf("stop checking scan")
return
case <-taskCheckTimer.C:
if s.DoneScanning() {
taskCheckTimer.Stop()
if report {
t := time.Now()
response := s.adminTask.Response.(*proto.SnapshotVerDelTaskResponse)
response.EndTime = &t
response.Status = proto.TaskSucceeds
response.Done = true
response.ID = s.ID
response.VolName = s.getTaskVolName()
response.VerSeq = s.getTaskVerSeq()
response.FileNum = s.currentStat.FileNum
response.DirNum = s.currentStat.DirNum
response.TotalInodeNum = s.currentStat.TotalInodeNum
response.ErrorSkippedNum = s.currentStat.ErrorSkippedNum
s.lcnode.scannerMutex.Lock()
delete(s.lcnode.snapshotScanners, s.ID)
s.Stop()
s.lcnode.scannerMutex.Unlock()
s.lcnode.respondToMaster(s.adminTask)
log.LogInfof("scan completed for task(%v)", s.adminTask)
} else {
log.LogInfof("first round scan completed for task(%v) without report", s.adminTask)
}
return
}
taskCheckTimer.Reset(dur)
}
}
}