You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

226 lines
6.8 KiB

package task
import (
"sync"
"time"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/wdclient"
"github.com/seaweedfs/seaweedfs/weed/worker/types"
)
// VolumeStateTracker tracks volume state changes and reconciles with master
type VolumeStateTracker struct {
masterClient *wdclient.MasterClient
reconcileInterval time.Duration
reservedVolumes map[uint32]*VolumeReservation
pendingChanges map[uint32]*VolumeChange
mutex sync.RWMutex
}
// NewVolumeStateTracker creates a new volume state tracker
func NewVolumeStateTracker(masterClient *wdclient.MasterClient, reconcileInterval time.Duration) *VolumeStateTracker {
return &VolumeStateTracker{
masterClient: masterClient,
reconcileInterval: reconcileInterval,
reservedVolumes: make(map[uint32]*VolumeReservation),
pendingChanges: make(map[uint32]*VolumeChange),
}
}
// ReserveVolume reserves a volume for a task
func (vst *VolumeStateTracker) ReserveVolume(volumeID uint32, taskID string) {
vst.mutex.Lock()
defer vst.mutex.Unlock()
reservation := &VolumeReservation{
VolumeID: volumeID,
TaskID: taskID,
ReservedAt: time.Now(),
ExpectedEnd: time.Now().Add(15 * time.Minute), // Default 15 min estimate
CapacityDelta: 0, // Will be updated based on task type
}
vst.reservedVolumes[volumeID] = reservation
glog.V(2).Infof("Reserved volume %d for task %s", volumeID, taskID)
}
// ReleaseVolume releases a volume reservation
func (vst *VolumeStateTracker) ReleaseVolume(volumeID uint32, taskID string) {
vst.mutex.Lock()
defer vst.mutex.Unlock()
if reservation, exists := vst.reservedVolumes[volumeID]; exists {
if reservation.TaskID == taskID {
delete(vst.reservedVolumes, volumeID)
glog.V(2).Infof("Released volume %d reservation for task %s", volumeID, taskID)
}
}
}
// RecordVolumeChange records a completed volume change
func (vst *VolumeStateTracker) RecordVolumeChange(volumeID uint32, taskType types.TaskType, taskID string) {
vst.mutex.Lock()
defer vst.mutex.Unlock()
changeType := ChangeTypeECEncoding
if taskType == types.TaskTypeVacuum {
changeType = ChangeTypeVacuumComplete
}
change := &VolumeChange{
VolumeID: volumeID,
ChangeType: changeType,
TaskID: taskID,
CompletedAt: time.Now(),
ReportedToMaster: false,
}
vst.pendingChanges[volumeID] = change
glog.V(1).Infof("Recorded volume change for volume %d: %s", volumeID, changeType)
}
// GetPendingChange returns pending change for a volume
func (vst *VolumeStateTracker) GetPendingChange(volumeID uint32) *VolumeChange {
vst.mutex.RLock()
defer vst.mutex.RUnlock()
return vst.pendingChanges[volumeID]
}
// GetVolumeReservation returns reservation for a volume
func (vst *VolumeStateTracker) GetVolumeReservation(volumeID uint32) *VolumeReservation {
vst.mutex.RLock()
defer vst.mutex.RUnlock()
return vst.reservedVolumes[volumeID]
}
// IsVolumeReserved checks if a volume is reserved
func (vst *VolumeStateTracker) IsVolumeReserved(volumeID uint32) bool {
vst.mutex.RLock()
defer vst.mutex.RUnlock()
_, exists := vst.reservedVolumes[volumeID]
return exists
}
// ReconcileWithMaster reconciles volume states with master server
func (vst *VolumeStateTracker) ReconcileWithMaster() {
vst.mutex.Lock()
defer vst.mutex.Unlock()
// Report pending changes to master
for volumeID, change := range vst.pendingChanges {
if vst.reportChangeToMaster(change) {
change.ReportedToMaster = true
delete(vst.pendingChanges, volumeID)
glog.V(1).Infof("Successfully reported volume change for volume %d to master", volumeID)
}
}
// Clean up expired reservations
vst.cleanupExpiredReservations()
}
// reportChangeToMaster reports a volume change to the master server
func (vst *VolumeStateTracker) reportChangeToMaster(change *VolumeChange) bool {
// Note: In a real implementation, this would make actual API calls to master
// For now, we'll simulate the reporting
switch change.ChangeType {
case ChangeTypeECEncoding:
return vst.reportECCompletion(change)
case ChangeTypeVacuumComplete:
return vst.reportVacuumCompletion(change)
}
return false
}
// reportECCompletion reports EC completion to master
func (vst *VolumeStateTracker) reportECCompletion(change *VolumeChange) bool {
// This would typically trigger the master to:
// 1. Update volume state to reflect EC encoding
// 2. Update capacity calculations
// 3. Redistribute volume assignments
glog.V(2).Infof("Reporting EC completion for volume %d", change.VolumeID)
// Simulate master API call
err := vst.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
// In real implementation, there would be a specific API call here
// For now, we simulate success
return nil
})
return err == nil
}
// reportVacuumCompletion reports vacuum completion to master
func (vst *VolumeStateTracker) reportVacuumCompletion(change *VolumeChange) bool {
// This would typically trigger the master to:
// 1. Update volume statistics
// 2. Update capacity calculations
// 3. Mark volume as recently vacuumed
glog.V(2).Infof("Reporting vacuum completion for volume %d", change.VolumeID)
// Simulate master API call
err := vst.masterClient.WithClient(false, func(client master_pb.SeaweedClient) error {
// In real implementation, there would be a specific API call here
// For now, we simulate success
return nil
})
return err == nil
}
// cleanupExpiredReservations removes expired volume reservations
func (vst *VolumeStateTracker) cleanupExpiredReservations() {
now := time.Now()
for volumeID, reservation := range vst.reservedVolumes {
if now.After(reservation.ExpectedEnd) {
delete(vst.reservedVolumes, volumeID)
glog.Warningf("Cleaned up expired reservation for volume %d (task %s)", volumeID, reservation.TaskID)
}
}
}
// GetAdjustedCapacity returns adjusted capacity considering in-progress tasks
func (vst *VolumeStateTracker) GetAdjustedCapacity(volumeID uint32, baseCapacity int64) int64 {
vst.mutex.RLock()
defer vst.mutex.RUnlock()
// Check for pending changes
if change := vst.pendingChanges[volumeID]; change != nil {
return change.NewCapacity
}
// Check for in-progress reservations
if reservation := vst.reservedVolumes[volumeID]; reservation != nil {
return baseCapacity + reservation.CapacityDelta
}
return baseCapacity
}
// GetStats returns statistics about volume state tracking
func (vst *VolumeStateTracker) GetStats() map[string]interface{} {
vst.mutex.RLock()
defer vst.mutex.RUnlock()
stats := make(map[string]interface{})
stats["reserved_volumes"] = len(vst.reservedVolumes)
stats["pending_changes"] = len(vst.pendingChanges)
changeTypeCounts := make(map[ChangeType]int)
for _, change := range vst.pendingChanges {
changeTypeCounts[change.ChangeType]++
}
stats["pending_by_type"] = changeTypeCounts
return stats
}