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
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
|
|
}
|