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.
		
		
		
		
		
			
		
			
				
					
					
						
							257 lines
						
					
					
						
							7.1 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							257 lines
						
					
					
						
							7.1 KiB
						
					
					
				| package topology | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" | |
| ) | |
| 
 | |
| // UpdateTopology updates the topology information from master | |
| func (at *ActiveTopology) UpdateTopology(topologyInfo *master_pb.TopologyInfo) error { | |
| 	at.mutex.Lock() | |
| 	defer at.mutex.Unlock() | |
| 
 | |
| 	at.topologyInfo = topologyInfo | |
| 	at.lastUpdated = time.Now() | |
| 
 | |
| 	// Rebuild structured topology | |
| 	at.nodes = make(map[string]*activeNode) | |
| 	at.disks = make(map[string]*activeDisk) | |
| 
 | |
| 	for _, dc := range topologyInfo.DataCenterInfos { | |
| 		for _, rack := range dc.RackInfos { | |
| 			for _, nodeInfo := range rack.DataNodeInfos { | |
| 				node := &activeNode{ | |
| 					nodeID:     nodeInfo.Id, | |
| 					dataCenter: dc.Id, | |
| 					rack:       rack.Id, | |
| 					nodeInfo:   nodeInfo, | |
| 					disks:      make(map[uint32]*activeDisk), | |
| 				} | |
| 
 | |
| 				// Add disks for this node | |
| 				for diskType, diskInfo := range nodeInfo.DiskInfos { | |
| 					disk := &activeDisk{ | |
| 						DiskInfo: &DiskInfo{ | |
| 							NodeID:     nodeInfo.Id, | |
| 							DiskID:     diskInfo.DiskId, | |
| 							DiskType:   diskType, | |
| 							DataCenter: dc.Id, | |
| 							Rack:       rack.Id, | |
| 							DiskInfo:   diskInfo, | |
| 						}, | |
| 					} | |
| 
 | |
| 					diskKey := fmt.Sprintf("%s:%d", nodeInfo.Id, diskInfo.DiskId) | |
| 					node.disks[diskInfo.DiskId] = disk | |
| 					at.disks[diskKey] = disk | |
| 				} | |
| 
 | |
| 				at.nodes[nodeInfo.Id] = node | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Rebuild performance indexes for O(1) lookups | |
| 	at.rebuildIndexes() | |
| 
 | |
| 	// Reassign task states to updated topology | |
| 	at.reassignTaskStates() | |
| 
 | |
| 	glog.V(1).Infof("ActiveTopology updated: %d nodes, %d disks, %d volume entries, %d EC shard entries", | |
| 		len(at.nodes), len(at.disks), len(at.volumeIndex), len(at.ecShardIndex)) | |
| 	return nil | |
| } | |
| 
 | |
| // GetAvailableDisks returns disks that can accept new tasks of the given type | |
| // NOTE: For capacity-aware operations, prefer GetDisksWithEffectiveCapacity | |
| func (at *ActiveTopology) GetAvailableDisks(taskType TaskType, excludeNodeID string) []*DiskInfo { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	var available []*DiskInfo | |
| 
 | |
| 	for _, disk := range at.disks { | |
| 		if disk.NodeID == excludeNodeID { | |
| 			continue // Skip excluded node | |
| 		} | |
| 
 | |
| 		if at.isDiskAvailable(disk, taskType) { | |
| 			// Create a copy with current load count and effective capacity | |
| 			diskCopy := *disk.DiskInfo | |
| 			diskCopy.LoadCount = len(disk.pendingTasks) + len(disk.assignedTasks) | |
| 			available = append(available, &diskCopy) | |
| 		} | |
| 	} | |
| 
 | |
| 	return available | |
| } | |
| 
 | |
| // HasRecentTaskForVolume checks if a volume had a recent task (to avoid immediate re-detection) | |
| func (at *ActiveTopology) HasRecentTaskForVolume(volumeID uint32, taskType TaskType) bool { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	for _, task := range at.recentTasks { | |
| 		if task.VolumeID == volumeID && task.TaskType == taskType { | |
| 			return true | |
| 		} | |
| 	} | |
| 
 | |
| 	return false | |
| } | |
| 
 | |
| // GetAllNodes returns information about all nodes (public interface) | |
| func (at *ActiveTopology) GetAllNodes() map[string]*master_pb.DataNodeInfo { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	result := make(map[string]*master_pb.DataNodeInfo) | |
| 	for nodeID, node := range at.nodes { | |
| 		result[nodeID] = node.nodeInfo | |
| 	} | |
| 	return result | |
| } | |
| 
 | |
| // GetTopologyInfo returns the current topology information (read-only access) | |
| func (at *ActiveTopology) GetTopologyInfo() *master_pb.TopologyInfo { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 	return at.topologyInfo | |
| } | |
| 
 | |
| // GetNodeDisks returns all disks for a specific node | |
| func (at *ActiveTopology) GetNodeDisks(nodeID string) []*DiskInfo { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	node, exists := at.nodes[nodeID] | |
| 	if !exists { | |
| 		return nil | |
| 	} | |
| 
 | |
| 	var disks []*DiskInfo | |
| 	for _, disk := range node.disks { | |
| 		diskCopy := *disk.DiskInfo | |
| 		diskCopy.LoadCount = len(disk.pendingTasks) + len(disk.assignedTasks) | |
| 		disks = append(disks, &diskCopy) | |
| 	} | |
| 
 | |
| 	return disks | |
| } | |
| 
 | |
| // rebuildIndexes rebuilds the volume and EC shard indexes for O(1) lookups | |
| func (at *ActiveTopology) rebuildIndexes() { | |
| 	// Clear existing indexes | |
| 	at.volumeIndex = make(map[uint32][]string) | |
| 	at.ecShardIndex = make(map[uint32][]string) | |
| 
 | |
| 	// Rebuild indexes from current topology | |
| 	for _, dc := range at.topologyInfo.DataCenterInfos { | |
| 		for _, rack := range dc.RackInfos { | |
| 			for _, nodeInfo := range rack.DataNodeInfos { | |
| 				for _, diskInfo := range nodeInfo.DiskInfos { | |
| 					diskKey := fmt.Sprintf("%s:%d", nodeInfo.Id, diskInfo.DiskId) | |
| 
 | |
| 					// Index volumes | |
| 					for _, volumeInfo := range diskInfo.VolumeInfos { | |
| 						volumeID := volumeInfo.Id | |
| 						at.volumeIndex[volumeID] = append(at.volumeIndex[volumeID], diskKey) | |
| 					} | |
| 
 | |
| 					// Index EC shards | |
| 					for _, ecShardInfo := range diskInfo.EcShardInfos { | |
| 						volumeID := ecShardInfo.Id | |
| 						at.ecShardIndex[volumeID] = append(at.ecShardIndex[volumeID], diskKey) | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 	} | |
| } | |
| 
 | |
| // GetVolumeLocations returns the disk locations for a volume using O(1) lookup | |
| func (at *ActiveTopology) GetVolumeLocations(volumeID uint32, collection string) []VolumeReplica { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	diskKeys, exists := at.volumeIndex[volumeID] | |
| 	if !exists { | |
| 		return []VolumeReplica{} | |
| 	} | |
| 
 | |
| 	var replicas []VolumeReplica | |
| 	for _, diskKey := range diskKeys { | |
| 		if disk, diskExists := at.disks[diskKey]; diskExists { | |
| 			// Verify collection matches (since index doesn't include collection) | |
| 			if at.volumeMatchesCollection(disk, volumeID, collection) { | |
| 				replicas = append(replicas, VolumeReplica{ | |
| 					ServerID:   disk.NodeID, | |
| 					DiskID:     disk.DiskID, | |
| 					DataCenter: disk.DataCenter, | |
| 					Rack:       disk.Rack, | |
| 				}) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return replicas | |
| } | |
| 
 | |
| // GetECShardLocations returns the disk locations for EC shards using O(1) lookup | |
| func (at *ActiveTopology) GetECShardLocations(volumeID uint32, collection string) []VolumeReplica { | |
| 	at.mutex.RLock() | |
| 	defer at.mutex.RUnlock() | |
| 
 | |
| 	diskKeys, exists := at.ecShardIndex[volumeID] | |
| 	if !exists { | |
| 		return []VolumeReplica{} | |
| 	} | |
| 
 | |
| 	var ecShards []VolumeReplica | |
| 	for _, diskKey := range diskKeys { | |
| 		if disk, diskExists := at.disks[diskKey]; diskExists { | |
| 			// Verify collection matches (since index doesn't include collection) | |
| 			if at.ecShardMatchesCollection(disk, volumeID, collection) { | |
| 				ecShards = append(ecShards, VolumeReplica{ | |
| 					ServerID:   disk.NodeID, | |
| 					DiskID:     disk.DiskID, | |
| 					DataCenter: disk.DataCenter, | |
| 					Rack:       disk.Rack, | |
| 				}) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return ecShards | |
| } | |
| 
 | |
| // volumeMatchesCollection checks if a volume on a disk matches the given collection | |
| func (at *ActiveTopology) volumeMatchesCollection(disk *activeDisk, volumeID uint32, collection string) bool { | |
| 	if disk.DiskInfo == nil || disk.DiskInfo.DiskInfo == nil { | |
| 		return false | |
| 	} | |
| 
 | |
| 	for _, volumeInfo := range disk.DiskInfo.DiskInfo.VolumeInfos { | |
| 		if volumeInfo.Id == volumeID && volumeInfo.Collection == collection { | |
| 			return true | |
| 		} | |
| 	} | |
| 	return false | |
| } | |
| 
 | |
| // ecShardMatchesCollection checks if EC shards on a disk match the given collection | |
| func (at *ActiveTopology) ecShardMatchesCollection(disk *activeDisk, volumeID uint32, collection string) bool { | |
| 	if disk.DiskInfo == nil || disk.DiskInfo.DiskInfo == nil { | |
| 		return false | |
| 	} | |
| 
 | |
| 	for _, ecShardInfo := range disk.DiskInfo.DiskInfo.EcShardInfos { | |
| 		if ecShardInfo.Id == volumeID && ecShardInfo.Collection == collection { | |
| 			return true | |
| 		} | |
| 	} | |
| 	return false | |
| }
 |