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.
		
		
		
		
		
			
		
			
				
					
					
						
							385 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							385 lines
						
					
					
						
							11 KiB
						
					
					
				| package dash | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"sort" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" | |
| ) | |
| 
 | |
| // GetClusterCollections retrieves cluster collections data | |
| func (s *AdminServer) GetClusterCollections() (*ClusterCollectionsData, error) { | |
| 	var collections []CollectionInfo | |
| 	var totalVolumes int | |
| 	var totalEcVolumes int | |
| 	var totalFiles int64 | |
| 	var totalSize int64 | |
| 	collectionMap := make(map[string]*CollectionInfo) | |
| 
 | |
| 	// Get actual collection information from volume data | |
| 	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 regular volumes | |
| 							for _, volInfo := range diskInfo.VolumeInfos { | |
| 								// Extract collection name from volume info | |
| 								collectionName := volInfo.Collection | |
| 								if collectionName == "" { | |
| 									collectionName = "default" // Default collection for volumes without explicit collection | |
| 								} | |
| 
 | |
| 								// Get disk type from volume info, default to hdd if empty | |
| 								diskType := volInfo.DiskType | |
| 								if diskType == "" { | |
| 									diskType = "hdd" | |
| 								} | |
| 
 | |
| 								// Get or create collection info | |
| 								if collection, exists := collectionMap[collectionName]; exists { | |
| 									collection.VolumeCount++ | |
| 									collection.FileCount += int64(volInfo.FileCount) | |
| 									collection.TotalSize += int64(volInfo.Size) | |
| 
 | |
| 									// Update data center if this collection spans multiple DCs | |
| 									if collection.DataCenter != dc.Id && collection.DataCenter != "multi" { | |
| 										collection.DataCenter = "multi" | |
| 									} | |
| 
 | |
| 									// Add disk type if not already present | |
| 									diskTypeExists := false | |
| 									for _, existingDiskType := range collection.DiskTypes { | |
| 										if existingDiskType == diskType { | |
| 											diskTypeExists = true | |
| 											break | |
| 										} | |
| 									} | |
| 									if !diskTypeExists { | |
| 										collection.DiskTypes = append(collection.DiskTypes, diskType) | |
| 									} | |
| 
 | |
| 									totalVolumes++ | |
| 									totalFiles += int64(volInfo.FileCount) | |
| 									totalSize += int64(volInfo.Size) | |
| 								} else { | |
| 									newCollection := CollectionInfo{ | |
| 										Name:          collectionName, | |
| 										DataCenter:    dc.Id, | |
| 										VolumeCount:   1, | |
| 										EcVolumeCount: 0, | |
| 										FileCount:     int64(volInfo.FileCount), | |
| 										TotalSize:     int64(volInfo.Size), | |
| 										DiskTypes:     []string{diskType}, | |
| 									} | |
| 									collectionMap[collectionName] = &newCollection | |
| 									totalVolumes++ | |
| 									totalFiles += int64(volInfo.FileCount) | |
| 									totalSize += int64(volInfo.Size) | |
| 								} | |
| 							} | |
| 
 | |
| 							// Process EC volumes | |
| 							ecVolumeMap := make(map[uint32]bool) // Track unique EC volumes to avoid double counting | |
| 							for _, ecShardInfo := range diskInfo.EcShardInfos { | |
| 								// Extract collection name from EC shard info | |
| 								collectionName := ecShardInfo.Collection | |
| 								if collectionName == "" { | |
| 									collectionName = "default" // Default collection for EC volumes without explicit collection | |
| 								} | |
| 
 | |
| 								// Only count each EC volume once (not per shard) | |
| 								if !ecVolumeMap[ecShardInfo.Id] { | |
| 									ecVolumeMap[ecShardInfo.Id] = true | |
| 
 | |
| 									// Get disk type from disk info, default to hdd if empty | |
| 									diskType := diskInfo.Type | |
| 									if diskType == "" { | |
| 										diskType = "hdd" | |
| 									} | |
| 
 | |
| 									// Get or create collection info | |
| 									if collection, exists := collectionMap[collectionName]; exists { | |
| 										collection.EcVolumeCount++ | |
| 
 | |
| 										// Update data center if this collection spans multiple DCs | |
| 										if collection.DataCenter != dc.Id && collection.DataCenter != "multi" { | |
| 											collection.DataCenter = "multi" | |
| 										} | |
| 
 | |
| 										// Add disk type if not already present | |
| 										diskTypeExists := false | |
| 										for _, existingDiskType := range collection.DiskTypes { | |
| 											if existingDiskType == diskType { | |
| 												diskTypeExists = true | |
| 												break | |
| 											} | |
| 										} | |
| 										if !diskTypeExists { | |
| 											collection.DiskTypes = append(collection.DiskTypes, diskType) | |
| 										} | |
| 
 | |
| 										totalEcVolumes++ | |
| 									} else { | |
| 										newCollection := CollectionInfo{ | |
| 											Name:          collectionName, | |
| 											DataCenter:    dc.Id, | |
| 											VolumeCount:   0, | |
| 											EcVolumeCount: 1, | |
| 											FileCount:     0, | |
| 											TotalSize:     0, | |
| 											DiskTypes:     []string{diskType}, | |
| 										} | |
| 										collectionMap[collectionName] = &newCollection | |
| 										totalEcVolumes++ | |
| 									} | |
| 								} | |
| 							} | |
| 						} | |
| 					} | |
| 				} | |
| 			} | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	// Convert map to slice | |
| 	for _, collection := range collectionMap { | |
| 		collections = append(collections, *collection) | |
| 	} | |
| 
 | |
| 	// Sort collections alphabetically by name | |
| 	sort.Slice(collections, func(i, j int) bool { | |
| 		return collections[i].Name < collections[j].Name | |
| 	}) | |
| 
 | |
| 	// If no collections found, show a message indicating no collections exist | |
| 	if len(collections) == 0 { | |
| 		// Return empty collections data instead of creating fake ones | |
| 		return &ClusterCollectionsData{ | |
| 			Collections:      []CollectionInfo{}, | |
| 			TotalCollections: 0, | |
| 			TotalVolumes:     0, | |
| 			TotalEcVolumes:   0, | |
| 			TotalFiles:       0, | |
| 			TotalSize:        0, | |
| 			LastUpdated:      time.Now(), | |
| 		}, nil | |
| 	} | |
| 
 | |
| 	return &ClusterCollectionsData{ | |
| 		Collections:      collections, | |
| 		TotalCollections: len(collections), | |
| 		TotalVolumes:     totalVolumes, | |
| 		TotalEcVolumes:   totalEcVolumes, | |
| 		TotalFiles:       totalFiles, | |
| 		TotalSize:        totalSize, | |
| 		LastUpdated:      time.Now(), | |
| 	}, nil | |
| } | |
| 
 | |
| // GetCollectionDetails retrieves detailed information for a specific collection including volumes and EC volumes | |
| func (s *AdminServer) GetCollectionDetails(collectionName string, page int, pageSize int, sortBy string, sortOrder string) (*CollectionDetailsData, error) { | |
| 	// Set defaults | |
| 	if page < 1 { | |
| 		page = 1 | |
| 	} | |
| 	if pageSize < 1 || pageSize > 1000 { | |
| 		pageSize = 25 | |
| 	} | |
| 	if sortBy == "" { | |
| 		sortBy = "volume_id" | |
| 	} | |
| 	if sortOrder == "" { | |
| 		sortOrder = "asc" | |
| 	} | |
| 
 | |
| 	var regularVolumes []VolumeWithTopology | |
| 	var ecVolumes []EcVolumeWithShards | |
| 	var totalFiles int64 | |
| 	var totalSize int64 | |
| 	dataCenters := make(map[string]bool) | |
| 	diskTypes := make(map[string]bool) | |
| 
 | |
| 	// Get regular volumes for this collection | |
| 	regularVolumeData, err := s.GetClusterVolumes(1, 10000, "volume_id", "asc", collectionName) // Get all volumes | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	regularVolumes = regularVolumeData.Volumes | |
| 	totalSize = regularVolumeData.TotalSize | |
| 
 | |
| 	// Calculate total files from regular volumes | |
| 	for _, vol := range regularVolumes { | |
| 		totalFiles += int64(vol.FileCount) | |
| 	} | |
| 
 | |
| 	// Collect data centers and disk types from regular volumes | |
| 	for _, vol := range regularVolumes { | |
| 		dataCenters[vol.DataCenter] = true | |
| 		diskTypes[vol.DiskType] = true | |
| 	} | |
| 
 | |
| 	// Get EC volumes for this collection | |
| 	ecVolumeData, err := s.GetClusterEcVolumes(1, 10000, "volume_id", "asc", collectionName) // Get all EC volumes | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	ecVolumes = ecVolumeData.EcVolumes | |
| 
 | |
| 	// Collect data centers from EC volumes | |
| 	for _, ecVol := range ecVolumes { | |
| 		for _, dc := range ecVol.DataCenters { | |
| 			dataCenters[dc] = true | |
| 		} | |
| 	} | |
| 
 | |
| 	// Combine all volumes for sorting and pagination | |
| 	type VolumeForSorting struct { | |
| 		Type          string // "regular" or "ec" | |
| 		RegularVolume *VolumeWithTopology | |
| 		EcVolume      *EcVolumeWithShards | |
| 	} | |
| 
 | |
| 	var allVolumes []VolumeForSorting | |
| 	for i := range regularVolumes { | |
| 		allVolumes = append(allVolumes, VolumeForSorting{ | |
| 			Type:          "regular", | |
| 			RegularVolume: ®ularVolumes[i], | |
| 		}) | |
| 	} | |
| 	for i := range ecVolumes { | |
| 		allVolumes = append(allVolumes, VolumeForSorting{ | |
| 			Type:     "ec", | |
| 			EcVolume: &ecVolumes[i], | |
| 		}) | |
| 	} | |
| 
 | |
| 	// Sort all volumes | |
| 	sort.Slice(allVolumes, func(i, j int) bool { | |
| 		var less bool | |
| 		switch sortBy { | |
| 		case "volume_id": | |
| 			var idI, idJ uint32 | |
| 			if allVolumes[i].Type == "regular" { | |
| 				idI = allVolumes[i].RegularVolume.Id | |
| 			} else { | |
| 				idI = allVolumes[i].EcVolume.VolumeID | |
| 			} | |
| 			if allVolumes[j].Type == "regular" { | |
| 				idJ = allVolumes[j].RegularVolume.Id | |
| 			} else { | |
| 				idJ = allVolumes[j].EcVolume.VolumeID | |
| 			} | |
| 			less = idI < idJ | |
| 		case "type": | |
| 			// Sort by type first (regular before ec), then by volume ID | |
| 			if allVolumes[i].Type == allVolumes[j].Type { | |
| 				var idI, idJ uint32 | |
| 				if allVolumes[i].Type == "regular" { | |
| 					idI = allVolumes[i].RegularVolume.Id | |
| 				} else { | |
| 					idI = allVolumes[i].EcVolume.VolumeID | |
| 				} | |
| 				if allVolumes[j].Type == "regular" { | |
| 					idJ = allVolumes[j].RegularVolume.Id | |
| 				} else { | |
| 					idJ = allVolumes[j].EcVolume.VolumeID | |
| 				} | |
| 				less = idI < idJ | |
| 			} else { | |
| 				less = allVolumes[i].Type < allVolumes[j].Type // "ec" < "regular" | |
| 			} | |
| 		default: | |
| 			// Default to volume ID sort | |
| 			var idI, idJ uint32 | |
| 			if allVolumes[i].Type == "regular" { | |
| 				idI = allVolumes[i].RegularVolume.Id | |
| 			} else { | |
| 				idI = allVolumes[i].EcVolume.VolumeID | |
| 			} | |
| 			if allVolumes[j].Type == "regular" { | |
| 				idJ = allVolumes[j].RegularVolume.Id | |
| 			} else { | |
| 				idJ = allVolumes[j].EcVolume.VolumeID | |
| 			} | |
| 			less = idI < idJ | |
| 		} | |
| 
 | |
| 		if sortOrder == "desc" { | |
| 			return !less | |
| 		} | |
| 		return less | |
| 	}) | |
| 
 | |
| 	// Apply pagination | |
| 	totalVolumesAndEc := len(allVolumes) | |
| 	totalPages := (totalVolumesAndEc + pageSize - 1) / pageSize | |
| 	startIndex := (page - 1) * pageSize | |
| 	endIndex := startIndex + pageSize | |
| 	if endIndex > totalVolumesAndEc { | |
| 		endIndex = totalVolumesAndEc | |
| 	} | |
| 
 | |
| 	if startIndex >= totalVolumesAndEc { | |
| 		startIndex = 0 | |
| 		endIndex = 0 | |
| 	} | |
| 
 | |
| 	// Extract paginated results | |
| 	var paginatedRegularVolumes []VolumeWithTopology | |
| 	var paginatedEcVolumes []EcVolumeWithShards | |
| 
 | |
| 	for i := startIndex; i < endIndex; i++ { | |
| 		if allVolumes[i].Type == "regular" { | |
| 			paginatedRegularVolumes = append(paginatedRegularVolumes, *allVolumes[i].RegularVolume) | |
| 		} else { | |
| 			paginatedEcVolumes = append(paginatedEcVolumes, *allVolumes[i].EcVolume) | |
| 		} | |
| 	} | |
| 
 | |
| 	// Convert maps to slices | |
| 	var dcList []string | |
| 	for dc := range dataCenters { | |
| 		dcList = append(dcList, dc) | |
| 	} | |
| 	sort.Strings(dcList) | |
| 
 | |
| 	var diskTypeList []string | |
| 	for diskType := range diskTypes { | |
| 		diskTypeList = append(diskTypeList, diskType) | |
| 	} | |
| 	sort.Strings(diskTypeList) | |
| 
 | |
| 	return &CollectionDetailsData{ | |
| 		CollectionName: collectionName, | |
| 		RegularVolumes: paginatedRegularVolumes, | |
| 		EcVolumes:      paginatedEcVolumes, | |
| 		TotalVolumes:   len(regularVolumes), | |
| 		TotalEcVolumes: len(ecVolumes), | |
| 		TotalFiles:     totalFiles, | |
| 		TotalSize:      totalSize, | |
| 		DataCenters:    dcList, | |
| 		DiskTypes:      diskTypeList, | |
| 		LastUpdated:    time.Now(), | |
| 		Page:           page, | |
| 		PageSize:       pageSize, | |
| 		TotalPages:     totalPages, | |
| 		SortBy:         sortBy, | |
| 		SortOrder:      sortOrder, | |
| 	}, nil | |
| }
 |