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.
		
		
		
		
		
			
		
			
				
					
					
						
							745 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							745 lines
						
					
					
						
							20 KiB
						
					
					
				| package dash | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"fmt" | |
| 	"sort" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" | |
| ) | |
| 
 | |
| // matchesCollection checks if a volume/EC volume collection matches the filter collection. | |
| // Handles the special case where empty collection ("") represents the "default" collection. | |
| func matchesCollection(volumeCollection, filterCollection string) bool { | |
| 	// Both empty means default collection matches default filter | |
| 	if volumeCollection == "" && filterCollection == "" { | |
| 		return true | |
| 	} | |
| 	// Direct string match for named collections | |
| 	return volumeCollection == filterCollection | |
| } | |
| 
 | |
| // GetClusterEcShards retrieves cluster EC shards data with pagination, sorting, and filtering | |
| func (s *AdminServer) GetClusterEcShards(page int, pageSize int, sortBy string, sortOrder string, collection string) (*ClusterEcShardsData, error) { | |
| 	// Set defaults | |
| 	if page < 1 { | |
| 		page = 1 | |
| 	} | |
| 	if pageSize < 1 || pageSize > 1000 { | |
| 		pageSize = 100 | |
| 	} | |
| 	if sortBy == "" { | |
| 		sortBy = "volume_id" | |
| 	} | |
| 	if sortOrder == "" { | |
| 		sortOrder = "asc" | |
| 	} | |
| 
 | |
| 	var ecShards []EcShardWithInfo | |
| 	volumeShardsMap := make(map[uint32]map[int]bool) // volumeId -> set of shards present | |
| 	volumesWithAllShards := 0 | |
| 	volumesWithMissingShards := 0 | |
| 
 | |
| 	// Get detailed EC shard information via gRPC | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 
 | |
| 		if resp.TopologyInfo != nil { | |
| 			for _, dc := range resp.TopologyInfo.DataCenterInfos { | |
| 				for _, rack := range dc.RackInfos { | |
| 					for _, node := range rack.DataNodeInfos { | |
| 						for _, diskInfo := range node.DiskInfos { | |
| 							// Process EC shard information | |
| 							for _, ecShardInfo := range diskInfo.EcShardInfos { | |
| 								volumeId := ecShardInfo.Id | |
| 
 | |
| 								// Initialize volume shards map if needed | |
| 								if volumeShardsMap[volumeId] == nil { | |
| 									volumeShardsMap[volumeId] = make(map[int]bool) | |
| 								} | |
| 
 | |
| 								// Create individual shard entries for each shard this server has | |
| 								shardBits := ecShardInfo.EcIndexBits | |
| 								for shardId := 0; shardId < erasure_coding.TotalShardsCount; shardId++ { | |
| 									if (shardBits & (1 << uint(shardId))) != 0 { | |
| 										// Mark this shard as present for this volume | |
| 										volumeShardsMap[volumeId][shardId] = true | |
| 
 | |
| 										ecShard := EcShardWithInfo{ | |
| 											VolumeID:     volumeId, | |
| 											ShardID:      uint32(shardId), | |
| 											Collection:   ecShardInfo.Collection, | |
| 											Size:         0, // EC shards don't have individual size in the API response | |
| 											Server:       node.Id, | |
| 											DataCenter:   dc.Id, | |
| 											Rack:         rack.Id, | |
| 											DiskType:     diskInfo.Type, | |
| 											ModifiedTime: 0, // Not available in current API | |
| 											EcIndexBits:  ecShardInfo.EcIndexBits, | |
| 											ShardCount:   getShardCount(ecShardInfo.EcIndexBits), | |
| 										} | |
| 										ecShards = append(ecShards, ecShard) | |
| 									} | |
| 								} | |
| 							} | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	// Calculate volume-level completeness (across all servers) | |
| 	volumeCompleteness := make(map[uint32]bool) | |
| 	volumeMissingShards := make(map[uint32][]int) | |
| 
 | |
| 	for volumeId, shardsPresent := range volumeShardsMap { | |
| 		var missingShards []int | |
| 		shardCount := len(shardsPresent) | |
| 
 | |
| 		// Find which shards are missing for this volume across ALL servers | |
| 		for shardId := 0; shardId < erasure_coding.TotalShardsCount; shardId++ { | |
| 			if !shardsPresent[shardId] { | |
| 				missingShards = append(missingShards, shardId) | |
| 			} | |
| 		} | |
| 
 | |
| 		isComplete := (shardCount == erasure_coding.TotalShardsCount) | |
| 		volumeCompleteness[volumeId] = isComplete | |
| 		volumeMissingShards[volumeId] = missingShards | |
| 
 | |
| 		if isComplete { | |
| 			volumesWithAllShards++ | |
| 		} else { | |
| 			volumesWithMissingShards++ | |
| 		} | |
| 	} | |
| 
 | |
| 	// Update completeness info for each shard based on volume-level completeness | |
| 	for i := range ecShards { | |
| 		volumeId := ecShards[i].VolumeID | |
| 		ecShards[i].IsComplete = volumeCompleteness[volumeId] | |
| 		ecShards[i].MissingShards = volumeMissingShards[volumeId] | |
| 	} | |
| 
 | |
| 	// Filter by collection if specified | |
| 	if collection != "" { | |
| 		var filteredShards []EcShardWithInfo | |
| 		for _, shard := range ecShards { | |
| 			if shard.Collection == collection { | |
| 				filteredShards = append(filteredShards, shard) | |
| 			} | |
| 		} | |
| 		ecShards = filteredShards | |
| 	} | |
| 
 | |
| 	// Sort the results | |
| 	sortEcShards(ecShards, sortBy, sortOrder) | |
| 
 | |
| 	// Calculate statistics for conditional display | |
| 	dataCenters := make(map[string]bool) | |
| 	racks := make(map[string]bool) | |
| 	collections := make(map[string]bool) | |
| 
 | |
| 	for _, shard := range ecShards { | |
| 		dataCenters[shard.DataCenter] = true | |
| 		racks[shard.Rack] = true | |
| 		if shard.Collection != "" { | |
| 			collections[shard.Collection] = true | |
| 		} | |
| 	} | |
| 
 | |
| 	// Pagination | |
| 	totalShards := len(ecShards) | |
| 	totalPages := (totalShards + pageSize - 1) / pageSize | |
| 	startIndex := (page - 1) * pageSize | |
| 	endIndex := startIndex + pageSize | |
| 	if endIndex > totalShards { | |
| 		endIndex = totalShards | |
| 	} | |
| 
 | |
| 	if startIndex >= totalShards { | |
| 		startIndex = 0 | |
| 		endIndex = 0 | |
| 	} | |
| 
 | |
| 	paginatedShards := ecShards[startIndex:endIndex] | |
| 
 | |
| 	// Build response | |
| 	data := &ClusterEcShardsData{ | |
| 		EcShards:     paginatedShards, | |
| 		TotalShards:  totalShards, | |
| 		TotalVolumes: len(volumeShardsMap), | |
| 		LastUpdated:  time.Now(), | |
| 
 | |
| 		// Pagination | |
| 		CurrentPage: page, | |
| 		TotalPages:  totalPages, | |
| 		PageSize:    pageSize, | |
| 
 | |
| 		// Sorting | |
| 		SortBy:    sortBy, | |
| 		SortOrder: sortOrder, | |
| 
 | |
| 		// Statistics | |
| 		DataCenterCount: len(dataCenters), | |
| 		RackCount:       len(racks), | |
| 		CollectionCount: len(collections), | |
| 
 | |
| 		// Conditional display flags | |
| 		ShowDataCenterColumn: len(dataCenters) > 1, | |
| 		ShowRackColumn:       len(racks) > 1, | |
| 		ShowCollectionColumn: len(collections) > 1 || collection != "", | |
| 
 | |
| 		// Filtering | |
| 		FilterCollection: collection, | |
| 
 | |
| 		// EC specific statistics | |
| 		ShardsPerVolume:          make(map[uint32]int), // This will be recalculated below | |
| 		VolumesWithAllShards:     volumesWithAllShards, | |
| 		VolumesWithMissingShards: volumesWithMissingShards, | |
| 	} | |
| 
 | |
| 	// Recalculate ShardsPerVolume for the response | |
| 	for volumeId, shardsPresent := range volumeShardsMap { | |
| 		data.ShardsPerVolume[volumeId] = len(shardsPresent) | |
| 	} | |
| 
 | |
| 	// Set single values when only one exists | |
| 	if len(dataCenters) == 1 { | |
| 		for dc := range dataCenters { | |
| 			data.SingleDataCenter = dc | |
| 			break | |
| 		} | |
| 	} | |
| 	if len(racks) == 1 { | |
| 		for rack := range racks { | |
| 			data.SingleRack = rack | |
| 			break | |
| 		} | |
| 	} | |
| 	if len(collections) == 1 { | |
| 		for col := range collections { | |
| 			data.SingleCollection = col | |
| 			break | |
| 		} | |
| 	} | |
| 
 | |
| 	return data, nil | |
| } | |
| 
 | |
| // GetClusterEcVolumes retrieves cluster EC volumes data grouped by volume ID with shard locations | |
| func (s *AdminServer) GetClusterEcVolumes(page int, pageSize int, sortBy string, sortOrder string, collection string) (*ClusterEcVolumesData, error) { | |
| 	// Set defaults | |
| 	if page < 1 { | |
| 		page = 1 | |
| 	} | |
| 	if pageSize < 1 || pageSize > 1000 { | |
| 		pageSize = 100 | |
| 	} | |
| 	if sortBy == "" { | |
| 		sortBy = "volume_id" | |
| 	} | |
| 	if sortOrder == "" { | |
| 		sortOrder = "asc" | |
| 	} | |
| 
 | |
| 	volumeData := make(map[uint32]*EcVolumeWithShards) | |
| 	totalShards := 0 | |
| 
 | |
| 	// Get detailed EC shard information via gRPC | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 
 | |
| 		if resp.TopologyInfo != nil { | |
| 			for _, dc := range resp.TopologyInfo.DataCenterInfos { | |
| 				for _, rack := range dc.RackInfos { | |
| 					for _, node := range rack.DataNodeInfos { | |
| 						for _, diskInfo := range node.DiskInfos { | |
| 							// Process EC shard information | |
| 							for _, ecShardInfo := range diskInfo.EcShardInfos { | |
| 								volumeId := ecShardInfo.Id | |
| 
 | |
| 								// Initialize volume data if needed | |
| 								if volumeData[volumeId] == nil { | |
| 									volumeData[volumeId] = &EcVolumeWithShards{ | |
| 										VolumeID:       volumeId, | |
| 										Collection:     ecShardInfo.Collection, | |
| 										TotalShards:    0, | |
| 										IsComplete:     false, | |
| 										MissingShards:  []int{}, | |
| 										ShardLocations: make(map[int]string), | |
| 										ShardSizes:     make(map[int]int64), | |
| 										DataCenters:    []string{}, | |
| 										Servers:        []string{}, | |
| 										Racks:          []string{}, | |
| 									} | |
| 								} | |
| 
 | |
| 								volume := volumeData[volumeId] | |
| 
 | |
| 								// Track data centers and servers | |
| 								dcExists := false | |
| 								for _, existingDc := range volume.DataCenters { | |
| 									if existingDc == dc.Id { | |
| 										dcExists = true | |
| 										break | |
| 									} | |
| 								} | |
| 								if !dcExists { | |
| 									volume.DataCenters = append(volume.DataCenters, dc.Id) | |
| 								} | |
| 
 | |
| 								serverExists := false | |
| 								for _, existingServer := range volume.Servers { | |
| 									if existingServer == node.Id { | |
| 										serverExists = true | |
| 										break | |
| 									} | |
| 								} | |
| 								if !serverExists { | |
| 									volume.Servers = append(volume.Servers, node.Id) | |
| 								} | |
| 
 | |
| 								// Track racks | |
| 								rackExists := false | |
| 								for _, existingRack := range volume.Racks { | |
| 									if existingRack == rack.Id { | |
| 										rackExists = true | |
| 										break | |
| 									} | |
| 								} | |
| 								if !rackExists { | |
| 									volume.Racks = append(volume.Racks, rack.Id) | |
| 								} | |
| 
 | |
| 								// Process each shard this server has for this volume | |
| 								shardBits := ecShardInfo.EcIndexBits | |
| 								for shardId := 0; shardId < erasure_coding.TotalShardsCount; shardId++ { | |
| 									if (shardBits & (1 << uint(shardId))) != 0 { | |
| 										// Record shard location | |
| 										volume.ShardLocations[shardId] = node.Id | |
| 										totalShards++ | |
| 									} | |
| 								} | |
| 							} | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	// Collect shard size information from volume servers | |
| 	for volumeId, volume := range volumeData { | |
| 		// Group servers by volume to minimize gRPC calls | |
| 		serverHasVolume := make(map[string]bool) | |
| 		for _, server := range volume.Servers { | |
| 			serverHasVolume[server] = true | |
| 		} | |
| 
 | |
| 		// Query each server for shard sizes | |
| 		for server := range serverHasVolume { | |
| 			err := s.WithVolumeServerClient(pb.ServerAddress(server), func(client volume_server_pb.VolumeServerClient) error { | |
| 				resp, err := client.VolumeEcShardsInfo(context.Background(), &volume_server_pb.VolumeEcShardsInfoRequest{ | |
| 					VolumeId: volumeId, | |
| 				}) | |
| 				if err != nil { | |
| 					glog.V(1).Infof("Failed to get EC shard info from %s for volume %d: %v", server, volumeId, err) | |
| 					return nil // Continue with other servers, don't fail the entire request | |
| 				} | |
| 
 | |
| 				// Update shard sizes | |
| 				for _, shardInfo := range resp.EcShardInfos { | |
| 					volume.ShardSizes[int(shardInfo.ShardId)] = shardInfo.Size | |
| 				} | |
| 
 | |
| 				return nil | |
| 			}) | |
| 			if err != nil { | |
| 				glog.V(1).Infof("Failed to connect to volume server %s: %v", server, err) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Calculate completeness for each volume | |
| 	completeVolumes := 0 | |
| 	incompleteVolumes := 0 | |
| 
 | |
| 	for _, volume := range volumeData { | |
| 		volume.TotalShards = len(volume.ShardLocations) | |
| 
 | |
| 		// Find missing shards | |
| 		var missingShards []int | |
| 		for shardId := 0; shardId < erasure_coding.TotalShardsCount; shardId++ { | |
| 			if _, exists := volume.ShardLocations[shardId]; !exists { | |
| 				missingShards = append(missingShards, shardId) | |
| 			} | |
| 		} | |
| 
 | |
| 		volume.MissingShards = missingShards | |
| 		volume.IsComplete = (len(missingShards) == 0) | |
| 
 | |
| 		if volume.IsComplete { | |
| 			completeVolumes++ | |
| 		} else { | |
| 			incompleteVolumes++ | |
| 		} | |
| 	} | |
| 
 | |
| 	// Convert map to slice | |
| 	var ecVolumes []EcVolumeWithShards | |
| 	for _, volume := range volumeData { | |
| 		// Filter by collection if specified | |
| 		if collection == "" || matchesCollection(volume.Collection, collection) { | |
| 			ecVolumes = append(ecVolumes, *volume) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Sort the results | |
| 	sortEcVolumes(ecVolumes, sortBy, sortOrder) | |
| 
 | |
| 	// Calculate statistics for conditional display | |
| 	dataCenters := make(map[string]bool) | |
| 	collections := make(map[string]bool) | |
| 
 | |
| 	for _, volume := range ecVolumes { | |
| 		for _, dc := range volume.DataCenters { | |
| 			dataCenters[dc] = true | |
| 		} | |
| 		if volume.Collection != "" { | |
| 			collections[volume.Collection] = true | |
| 		} | |
| 	} | |
| 
 | |
| 	// Pagination | |
| 	totalVolumes := len(ecVolumes) | |
| 	totalPages := (totalVolumes + pageSize - 1) / pageSize | |
| 	startIndex := (page - 1) * pageSize | |
| 	endIndex := startIndex + pageSize | |
| 	if endIndex > totalVolumes { | |
| 		endIndex = totalVolumes | |
| 	} | |
| 
 | |
| 	if startIndex >= totalVolumes { | |
| 		startIndex = 0 | |
| 		endIndex = 0 | |
| 	} | |
| 
 | |
| 	paginatedVolumes := ecVolumes[startIndex:endIndex] | |
| 
 | |
| 	// Build response | |
| 	data := &ClusterEcVolumesData{ | |
| 		EcVolumes:    paginatedVolumes, | |
| 		TotalVolumes: totalVolumes, | |
| 		LastUpdated:  time.Now(), | |
| 
 | |
| 		// Pagination | |
| 		Page:       page, | |
| 		PageSize:   pageSize, | |
| 		TotalPages: totalPages, | |
| 
 | |
| 		// Sorting | |
| 		SortBy:    sortBy, | |
| 		SortOrder: sortOrder, | |
| 
 | |
| 		// Filtering | |
| 		Collection: collection, | |
| 
 | |
| 		// Conditional display flags | |
| 		ShowDataCenterColumn: len(dataCenters) > 1, | |
| 		ShowRackColumn:       false, // We don't track racks in this view for simplicity | |
| 		ShowCollectionColumn: len(collections) > 1 || collection != "", | |
| 
 | |
| 		// Statistics | |
| 		CompleteVolumes:   completeVolumes, | |
| 		IncompleteVolumes: incompleteVolumes, | |
| 		TotalShards:       totalShards, | |
| 	} | |
| 
 | |
| 	return data, nil | |
| } | |
| 
 | |
| // sortEcVolumes sorts EC volumes based on the specified field and order | |
| func sortEcVolumes(volumes []EcVolumeWithShards, sortBy string, sortOrder string) { | |
| 	sort.Slice(volumes, func(i, j int) bool { | |
| 		var less bool | |
| 		switch sortBy { | |
| 		case "volume_id": | |
| 			less = volumes[i].VolumeID < volumes[j].VolumeID | |
| 		case "collection": | |
| 			if volumes[i].Collection == volumes[j].Collection { | |
| 				less = volumes[i].VolumeID < volumes[j].VolumeID | |
| 			} else { | |
| 				less = volumes[i].Collection < volumes[j].Collection | |
| 			} | |
| 		case "total_shards": | |
| 			if volumes[i].TotalShards == volumes[j].TotalShards { | |
| 				less = volumes[i].VolumeID < volumes[j].VolumeID | |
| 			} else { | |
| 				less = volumes[i].TotalShards < volumes[j].TotalShards | |
| 			} | |
| 		case "completeness": | |
| 			// Complete volumes first, then by volume ID | |
| 			if volumes[i].IsComplete == volumes[j].IsComplete { | |
| 				less = volumes[i].VolumeID < volumes[j].VolumeID | |
| 			} else { | |
| 				less = volumes[i].IsComplete && !volumes[j].IsComplete | |
| 			} | |
| 		default: | |
| 			less = volumes[i].VolumeID < volumes[j].VolumeID | |
| 		} | |
| 
 | |
| 		if sortOrder == "desc" { | |
| 			return !less | |
| 		} | |
| 		return less | |
| 	}) | |
| } | |
| 
 | |
| // getShardCount returns the number of shards represented by the bitmap | |
| func getShardCount(ecIndexBits uint32) int { | |
| 	count := 0 | |
| 	for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 		if (ecIndexBits & (1 << uint(i))) != 0 { | |
| 			count++ | |
| 		} | |
| 	} | |
| 	return count | |
| } | |
| 
 | |
| // getMissingShards returns a slice of missing shard IDs for a volume | |
| func getMissingShards(ecIndexBits uint32) []int { | |
| 	var missing []int | |
| 	for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 		if (ecIndexBits & (1 << uint(i))) == 0 { | |
| 			missing = append(missing, i) | |
| 		} | |
| 	} | |
| 	return missing | |
| } | |
| 
 | |
| // sortEcShards sorts EC shards based on the specified field and order | |
| func sortEcShards(shards []EcShardWithInfo, sortBy string, sortOrder string) { | |
| 	sort.Slice(shards, func(i, j int) bool { | |
| 		var less bool | |
| 		switch sortBy { | |
| 		case "shard_id": | |
| 			less = shards[i].ShardID < shards[j].ShardID | |
| 		case "server": | |
| 			if shards[i].Server == shards[j].Server { | |
| 				less = shards[i].ShardID < shards[j].ShardID // Secondary sort by shard ID | |
| 			} else { | |
| 				less = shards[i].Server < shards[j].Server | |
| 			} | |
| 		case "data_center": | |
| 			if shards[i].DataCenter == shards[j].DataCenter { | |
| 				less = shards[i].ShardID < shards[j].ShardID // Secondary sort by shard ID | |
| 			} else { | |
| 				less = shards[i].DataCenter < shards[j].DataCenter | |
| 			} | |
| 		case "rack": | |
| 			if shards[i].Rack == shards[j].Rack { | |
| 				less = shards[i].ShardID < shards[j].ShardID // Secondary sort by shard ID | |
| 			} else { | |
| 				less = shards[i].Rack < shards[j].Rack | |
| 			} | |
| 		default: | |
| 			less = shards[i].ShardID < shards[j].ShardID | |
| 		} | |
| 
 | |
| 		if sortOrder == "desc" { | |
| 			return !less | |
| 		} | |
| 		return less | |
| 	}) | |
| } | |
| 
 | |
| // GetEcVolumeDetails retrieves detailed information about a specific EC volume | |
| func (s *AdminServer) GetEcVolumeDetails(volumeID uint32, sortBy string, sortOrder string) (*EcVolumeDetailsData, error) { | |
| 	// Set defaults | |
| 	if sortBy == "" { | |
| 		sortBy = "shard_id" | |
| 	} | |
| 	if sortOrder == "" { | |
| 		sortOrder = "asc" | |
| 	} | |
| 
 | |
| 	var shards []EcShardWithInfo | |
| 	var collection string | |
| 	dataCenters := make(map[string]bool) | |
| 	servers := make(map[string]bool) | |
| 
 | |
| 	// Get detailed EC shard information for the specific volume via gRPC | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 
 | |
| 		if resp.TopologyInfo != nil { | |
| 			for _, dc := range resp.TopologyInfo.DataCenterInfos { | |
| 				for _, rack := range dc.RackInfos { | |
| 					for _, node := range rack.DataNodeInfos { | |
| 						for _, diskInfo := range node.DiskInfos { | |
| 							// Process EC shard information for this specific volume | |
| 							for _, ecShardInfo := range diskInfo.EcShardInfos { | |
| 								if ecShardInfo.Id == volumeID { | |
| 									collection = ecShardInfo.Collection | |
| 									dataCenters[dc.Id] = true | |
| 									servers[node.Id] = true | |
| 
 | |
| 									// Create individual shard entries for each shard this server has | |
| 									shardBits := ecShardInfo.EcIndexBits | |
| 									for shardId := 0; shardId < erasure_coding.TotalShardsCount; shardId++ { | |
| 										if (shardBits & (1 << uint(shardId))) != 0 { | |
| 											ecShard := EcShardWithInfo{ | |
| 												VolumeID:     ecShardInfo.Id, | |
| 												ShardID:      uint32(shardId), | |
| 												Collection:   ecShardInfo.Collection, | |
| 												Size:         0, // EC shards don't have individual size in the API response | |
| 												Server:       node.Id, | |
| 												DataCenter:   dc.Id, | |
| 												Rack:         rack.Id, | |
| 												DiskType:     diskInfo.Type, | |
| 												ModifiedTime: 0, // Not available in current API | |
| 												EcIndexBits:  ecShardInfo.EcIndexBits, | |
| 												ShardCount:   getShardCount(ecShardInfo.EcIndexBits), | |
| 											} | |
| 											shards = append(shards, ecShard) | |
| 										} | |
| 									} | |
| 								} | |
| 							} | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	if len(shards) == 0 { | |
| 		return nil, fmt.Errorf("EC volume %d not found", volumeID) | |
| 	} | |
| 
 | |
| 	// Collect shard size information from volume servers | |
| 	shardSizeMap := make(map[string]map[uint32]uint64) // server -> shardId -> size | |
| 	for _, shard := range shards { | |
| 		server := shard.Server | |
| 		if _, exists := shardSizeMap[server]; !exists { | |
| 			// Query this server for shard sizes | |
| 			err := s.WithVolumeServerClient(pb.ServerAddress(server), func(client volume_server_pb.VolumeServerClient) error { | |
| 				resp, err := client.VolumeEcShardsInfo(context.Background(), &volume_server_pb.VolumeEcShardsInfoRequest{ | |
| 					VolumeId: volumeID, | |
| 				}) | |
| 				if err != nil { | |
| 					glog.V(1).Infof("Failed to get EC shard info from %s for volume %d: %v", server, volumeID, err) | |
| 					return nil // Continue with other servers, don't fail the entire request | |
| 				} | |
| 
 | |
| 				// Store shard sizes for this server | |
| 				shardSizeMap[server] = make(map[uint32]uint64) | |
| 				for _, shardInfo := range resp.EcShardInfos { | |
| 					shardSizeMap[server][shardInfo.ShardId] = uint64(shardInfo.Size) | |
| 				} | |
| 
 | |
| 				return nil | |
| 			}) | |
| 			if err != nil { | |
| 				glog.V(1).Infof("Failed to connect to volume server %s: %v", server, err) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Update shard sizes in the shards array | |
| 	for i := range shards { | |
| 		server := shards[i].Server | |
| 		shardId := shards[i].ShardID | |
| 		if serverSizes, exists := shardSizeMap[server]; exists { | |
| 			if size, exists := serverSizes[shardId]; exists { | |
| 				shards[i].Size = size | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	// Calculate completeness based on unique shard IDs | |
| 	foundShards := make(map[int]bool) | |
| 	for _, shard := range shards { | |
| 		foundShards[int(shard.ShardID)] = true | |
| 	} | |
| 
 | |
| 	totalUniqueShards := len(foundShards) | |
| 	isComplete := (totalUniqueShards == erasure_coding.TotalShardsCount) | |
| 
 | |
| 	// Calculate missing shards | |
| 	var missingShards []int | |
| 	for i := 0; i < erasure_coding.TotalShardsCount; i++ { | |
| 		if !foundShards[i] { | |
| 			missingShards = append(missingShards, i) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Update completeness info for each shard | |
| 	for i := range shards { | |
| 		shards[i].IsComplete = isComplete | |
| 		shards[i].MissingShards = missingShards | |
| 	} | |
| 
 | |
| 	// Sort shards based on parameters | |
| 	sortEcShards(shards, sortBy, sortOrder) | |
| 
 | |
| 	// Convert maps to slices | |
| 	var dcList []string | |
| 	for dc := range dataCenters { | |
| 		dcList = append(dcList, dc) | |
| 	} | |
| 	var serverList []string | |
| 	for server := range servers { | |
| 		serverList = append(serverList, server) | |
| 	} | |
| 
 | |
| 	data := &EcVolumeDetailsData{ | |
| 		VolumeID:      volumeID, | |
| 		Collection:    collection, | |
| 		Shards:        shards, | |
| 		TotalShards:   totalUniqueShards, | |
| 		IsComplete:    isComplete, | |
| 		MissingShards: missingShards, | |
| 		DataCenters:   dcList, | |
| 		Servers:       serverList, | |
| 		LastUpdated:   time.Now(), | |
| 		SortBy:        sortBy, | |
| 		SortOrder:     sortOrder, | |
| 	} | |
| 
 | |
| 	return data, nil | |
| }
 |