From b87eab174d1756b5c934fc2eacba73ddfe462c7f Mon Sep 17 00:00:00 2001 From: hakhandelwal11 Date: Tue, 19 May 2026 16:47:32 +0530 Subject: [PATCH] feat: add region flag --- pkg/cmd/gpucreate/gpucreate.go | 109 ++++++++++++++++- pkg/cmd/gpusearch/gpusearch.go | 181 +++++++++++++++++----------- pkg/cmd/gpusearch/gpusearch_test.go | 34 +++--- 3 files changed, 233 insertions(+), 91 deletions(-) diff --git a/pkg/cmd/gpucreate/gpucreate.go b/pkg/cmd/gpucreate/gpucreate.go index 9950fe86a..445593236 100644 --- a/pkg/cmd/gpucreate/gpucreate.go +++ b/pkg/cmd/gpucreate/gpucreate.go @@ -126,6 +126,7 @@ type CreateResult struct { // searchFilterFlags holds the search filter flag values for create type searchFilterFlags struct { gpuName string + region string provider string minVRAM float64 minTotalVRAM float64 @@ -141,7 +142,7 @@ type searchFilterFlags struct { // hasUserFilters returns true if the user specified any search filter flags func (f *searchFilterFlags) hasUserFilters() bool { - return f.gpuName != "" || f.provider != "" || f.minVRAM > 0 || f.minTotalVRAM > 0 || + return f.gpuName != "" || f.region != "" || f.provider != "" || f.minVRAM > 0 || f.minTotalVRAM > 0 || f.minCapability > 0 || f.minDisk > 0 || f.maxBootTime > 0 || f.stoppable || f.rebootable || f.flexPorts } @@ -230,6 +231,13 @@ func NewCmdGPUCreate(t *terminal.Terminal, gpuCreateStore GPUCreateStore) *cobra ComposeFile: composeFile, LaunchableID: launchableID, LaunchableInfo: launchableInfo, + Region: filters.region, + } + + if filters.region != "" { + if err := validateRegionExists(filters.region, gpuCreateStore); err != nil { + return err + } } opts.InstanceTypes, err = resolveInstanceTypes(cmd, gpuCreateStore, opts, types, &filters) @@ -237,6 +245,12 @@ func NewCmdGPUCreate(t *terminal.Terminal, gpuCreateStore GPUCreateStore) *cobra return err } + if filters.region != "" { + if err := validateRegion(filters.region, opts.InstanceTypes, gpuCreateStore); err != nil { + return err + } + } + if dryRun { return runDryRun(t, gpuCreateStore, opts.InstanceTypes, &filters) } @@ -278,6 +292,7 @@ func registerCreateFlags(cmd *cobra.Command, name, instanceTypes *string, count, cmd.Flags().StringVar(containerImage, "container-image", "", "Container image URL (required for container mode)") cmd.Flags().StringVar(composeFile, "compose-file", "", "Docker compose file path or URL (required for compose mode)") cmd.Flags().StringVarP(launchable, "launchable", "l", "", "Launchable ID or URL to deploy (e.g., env-XXX or console URL)") + cmd.Flags().StringVarP(&filters.region, "region", "r", "", "Region/location to deploy the instance (e.g., us-east-1, us-central1)") cmd.Flags().StringVarP(&filters.gpuName, "gpu-name", "g", "", "Filter by GPU name (e.g., A100, H100)") cmd.Flags().StringVar(&filters.provider, "provider", "", "Filter by provider/cloud (e.g., aws, gcp)") @@ -315,6 +330,7 @@ type GPUCreateOptions struct { ComposeFile string LaunchableID string LaunchableInfo *store.LaunchableResponse // populated when LaunchableID is set + Region string } // parseLaunchableID extracts a launchable ID from either a raw ID (env-XXX) or @@ -493,7 +509,7 @@ func searchInstances(s GPUCreateStore, filters *searchFilterFlags) ([]gpusearch. } instances := gpusearch.ProcessInstances(response.Items) - filtered := gpusearch.FilterInstances(instances, filters.gpuName, filters.provider, "", filters.minVRAM, + filtered := gpusearch.FilterInstances(instances, filters.gpuName, filters.region, filters.provider, "", filters.minVRAM, minTotalVRAM, minCapability, 0, minDisk, 0, maxBootTime, filters.stoppable, filters.rebootable, filters.flexPorts, true) gpusearch.SortInstances(filtered, sortBy, filters.descending) @@ -539,6 +555,91 @@ func runDryRun(t *terminal.Terminal, s GPUCreateStore, specs []InstanceSpec, fil return nil } +// validateRegionExists checks that the given region appears in at least one instance type in the catalog. +func validateRegionExists(region string, store GPUCreateStore) error { + response, err := store.GetInstanceTypes(false) + if err != nil { + return breverrors.WrapAndTrace(err) + } + if response == nil || len(response.Items) == 0 { + return nil + } + + regionLower := strings.ToLower(region) + for _, item := range response.Items { + if typeSupportsRegion(item, regionLower) { + return nil + } + } + + return breverrors.NewValidationError( + fmt.Sprintf("region %q is not offered by any instance type -- use 'brev search --json' to list valid regions", + region), + ) +} + +// validateRegion checks that every requested instance type is available in the given region. +func validateRegion(region string, types []InstanceSpec, store GPUCreateStore) error { + if len(types) == 0 { + return nil + } + + response, err := store.GetInstanceTypes(false) + if err != nil { + return breverrors.WrapAndTrace(err) + } + if response == nil || len(response.Items) == 0 { + return nil + } + + catalog := make(map[string]gpusearch.InstanceType, len(response.Items)) + for _, item := range response.Items { + catalog[item.Type] = item + } + + regionLower := strings.ToLower(region) + var unsupported []string + var unknown []string + + for _, spec := range types { + item, ok := catalog[spec.Type] + if !ok { + unknown = append(unknown, spec.Type) + continue + } + if !typeSupportsRegion(item, regionLower) { + unsupported = append(unsupported, spec.Type) + } + } + + if len(unknown) > 0 { + return breverrors.NewValidationError( + fmt.Sprintf("unknown instance type(s) %s -- use 'brev search' to list available types", strings.Join(unknown, ", ")), + ) + } + if len(unsupported) > 0 { + return breverrors.NewValidationError( + fmt.Sprintf("region %q is not available for instance type(s) %s -- use 'brev search --region %s' to find compatible types", + region, strings.Join(unsupported, ", "), region), + ) + } + return nil +} + +// typeSupportsRegion reports whether an instance type lists the given region (already lowercased) +// in either its primary Location or AvailableLocations, using substring matching. +func typeSupportsRegion(item gpusearch.InstanceType, regionLower string) bool { + if strings.Contains(strings.ToLower(item.Location), regionLower) { + return true + } + for _, loc := range item.AvailableLocations { + if strings.Contains(strings.ToLower(loc), regionLower) { + return true + } + } + return false +} + // orDefault returns val if it's non-zero, otherwise returns def func orDefault(val, def float64) float64 { if val > 0 { @@ -993,6 +1094,10 @@ func (c *createContext) createWorkspace(name string, spec InstanceSpec) (*entity } } + if c.opts.Region != "" { + cwOptions.Location = c.opts.Region + } + // Apply launchable config or build mode if c.opts.LaunchableID != "" { applyLaunchableConfig(cwOptions, c.opts.LaunchableID, c.opts.LaunchableInfo) diff --git a/pkg/cmd/gpusearch/gpusearch.go b/pkg/cmd/gpusearch/gpusearch.go index 612b26a4a..7b133d09d 100644 --- a/pkg/cmd/gpusearch/gpusearch.go +++ b/pkg/cmd/gpusearch/gpusearch.go @@ -122,6 +122,10 @@ Features column shows instance capabilities: # Filter by GPU name (case-insensitive, partial match) brev search gpu --gpu-name A100 + # Filter by region/location + brev search gpu --region us-east-1 + brev search gpu --region us-central + # Filter by minimum VRAM per GPU (in GB) brev search gpu --min-vram 24 @@ -140,6 +144,9 @@ Features column shows instance capabilities: # Filter by provider brev search cpu --provider aws + # Filter by region/location + brev search cpu --region us-east-1 + # Filter by minimum RAM brev search cpu --min-ram 64 @@ -153,6 +160,7 @@ Features column shows instance capabilities: // sharedFlags holds flags shared between gpu and cpu subcommands type sharedFlags struct { + region string provider string arch string minVCPU int @@ -169,6 +177,7 @@ type sharedFlags struct { // addSharedFlags adds common flags to a command func addSharedFlags(cmd *cobra.Command, f *sharedFlags) { + cmd.Flags().StringVarP(&f.region, "region", "r", "", "Filter by region/location (case-insensitive, partial match, e.g., us-east-1, us-central1)") cmd.Flags().StringVarP(&f.provider, "provider", "p", "", "Filter by provider/cloud (case-insensitive, partial match)") cmd.Flags().StringVar(&f.arch, "arch", "", "Filter by architecture (e.g., x86_64, arm64)") cmd.Flags().IntVar(&f.minVCPU, "min-vcpu", 0, "Minimum number of vCPUs") @@ -203,7 +212,7 @@ func NewCmdGPUSearch(t *terminal.Terminal, store GPUSearchStore) *cobra.Command Example: gpuExample, RunE: func(cmd *cobra.Command, args []string) error { // Default behavior: GPU search - return RunGPUSearch(t, store, gpuName, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide) + return RunGPUSearch(t, store, gpuName, shared.region, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide) }, } @@ -237,7 +246,7 @@ func newCmdGPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm Short: "Search GPU instance types", Example: gpuExample, RunE: func(cmd *cobra.Command, args []string) error { - return RunGPUSearch(t, store, gpuName, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide) + return RunGPUSearch(t, store, gpuName, shared.region, shared.provider, shared.arch, minVRAM, minTotalVRAM, minCapability, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput, wide) }, } @@ -261,7 +270,7 @@ func newCmdCPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm Short: "Search CPU-only instance types", Example: cpuExample, RunE: func(cmd *cobra.Command, args []string) error { - return RunCPUSearch(t, store, shared.provider, shared.arch, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput) + return RunCPUSearch(t, store, shared.region, shared.provider, shared.arch, shared.minRAM, shared.minDisk, shared.minVCPU, shared.maxBootTime, shared.stoppable, shared.rebootable, shared.flexPorts, shared.sortBy, shared.descending, shared.jsonOutput) }, } @@ -272,28 +281,30 @@ func newCmdCPUSubcommand(t *terminal.Terminal, store GPUSearchStore) *cobra.Comm // GPUInstanceInfo holds processed GPU instance information for display type GPUInstanceInfo struct { - Type string `json:"type"` - Cloud string `json:"cloud"` // Underlying cloud (e.g., hyperstack, aws, gcp) - Provider string `json:"provider"` // Provider/aggregator (e.g., shadeform, aws, gcp) - GPUName string `json:"gpu_name"` - GPUCount int `json:"gpu_count"` - VRAMPerGPU float64 `json:"vram_per_gpu_gb"` - TotalVRAM float64 `json:"total_vram_gb"` - Capability float64 `json:"capability"` - VCPUs int `json:"vcpus"` - Memory string `json:"memory"` - RAMInGB float64 `json:"ram_gb"` - Arch string `json:"arch"` - DiskMin float64 `json:"disk_min_gb"` - DiskMax float64 `json:"disk_max_gb"` - DiskPricePerMo float64 `json:"disk_price_per_gb_mo,omitempty"` // $/GB/month for flexible storage - BootTime int `json:"boot_time_seconds"` - Stoppable bool `json:"stoppable"` - Rebootable bool `json:"rebootable"` - FlexPorts bool `json:"flex_ports"` - TargetDisk float64 `json:"target_disk_gb,omitempty"` - PricePerHour float64 `json:"price_per_hour"` - Manufacturer string `json:"-"` // exclude from JSON output + Type string `json:"type"` + Cloud string `json:"cloud"` // Underlying cloud (e.g., hyperstack, aws, gcp) + Provider string `json:"provider"` // Provider/aggregator (e.g., shadeform, aws, gcp) + GPUName string `json:"gpu_name"` + GPUCount int `json:"gpu_count"` + VRAMPerGPU float64 `json:"vram_per_gpu_gb"` + TotalVRAM float64 `json:"total_vram_gb"` + Capability float64 `json:"capability"` + VCPUs int `json:"vcpus"` + Memory string `json:"memory"` + RAMInGB float64 `json:"ram_gb"` + Arch string `json:"arch"` + Region string `json:"region,omitempty"` + AvailableRegions []string `json:"available_regions,omitempty"` + DiskMin float64 `json:"disk_min_gb"` + DiskMax float64 `json:"disk_max_gb"` + DiskPricePerMo float64 `json:"disk_price_per_gb_mo,omitempty"` // $/GB/month for flexible storage + BootTime int `json:"boot_time_seconds"` + Stoppable bool `json:"stoppable"` + Rebootable bool `json:"rebootable"` + FlexPorts bool `json:"flex_ports"` + TargetDisk float64 `json:"target_disk_gb,omitempty"` + PricePerHour float64 `json:"price_per_hour"` + Manufacturer string `json:"-"` // exclude from JSON output } // IsStdoutPiped returns true if stdout is being piped (not a terminal) @@ -303,7 +314,7 @@ func IsStdoutPiped() bool { } // RunGPUSearch executes the GPU search with filters and sorting -func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput, wide bool) error { +func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, region, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput, wide bool) error { if err := validateSortOption(sortBy); err != nil { return err } @@ -322,7 +333,7 @@ func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider, instances := ProcessInstances(response.Items) // Filter to GPU-only instances - filtered := FilterInstances(instances, gpuName, provider, arch, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false) + filtered := FilterInstances(instances, gpuName, region, provider, arch, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false) if len(filtered) == 0 { return displayEmptyResults(t, "No GPU instances match the specified filters", jsonOutput, piped) @@ -334,7 +345,7 @@ func RunGPUSearch(t *terminal.Terminal, store GPUSearchStore, gpuName, provider, } // RunCPUSearch executes the CPU search with filters and sorting -func RunCPUSearch(t *terminal.Terminal, store GPUSearchStore, provider, arch string, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput bool) error { +func RunCPUSearch(t *terminal.Terminal, store GPUSearchStore, region, provider, arch string, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool, sortBy string, descending, jsonOutput bool) error { if err := validateSortOption(sortBy); err != nil { return err } @@ -353,7 +364,7 @@ func RunCPUSearch(t *terminal.Terminal, store GPUSearchStore, provider, arch str instances := ProcessInstances(response.Items) // Filter to CPU-only instances - filtered := FilterCPUInstances(instances, provider, arch, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts) + filtered := FilterCPUInstances(instances, region, provider, arch, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts) if len(filtered) == 0 { return displayEmptyResults(t, "No CPU instances match the specified filters", jsonOutput, piped) @@ -731,24 +742,26 @@ func ProcessInstances(items []InstanceType) []GPUInstanceInfo { if len(item.SupportedGPUs) == 0 { // CPU-only instance instances = append(instances, GPUInstanceInfo{ - Type: item.Type, - Cloud: extractCloud(item.Type, item.Provider), - Provider: item.Provider, - GPUName: "-", - GPUCount: 0, - VCPUs: item.VCPU, - Memory: item.Memory, - RAMInGB: ramInGB, - Arch: arch, - DiskMin: diskMin, - DiskMax: diskMax, - DiskPricePerMo: diskPricePerMo, - BootTime: bootTime, - Stoppable: item.Stoppable, - Rebootable: item.Rebootable, - FlexPorts: item.CanModifyFirewallRules, - PricePerHour: price, - Manufacturer: "cpu", + Type: item.Type, + Cloud: extractCloud(item.Type, item.Provider), + Provider: item.Provider, + GPUName: "-", + GPUCount: 0, + VCPUs: item.VCPU, + Memory: item.Memory, + RAMInGB: ramInGB, + Arch: arch, + Region: item.Location, + AvailableRegions: item.AvailableLocations, + DiskMin: diskMin, + DiskMax: diskMax, + DiskPricePerMo: diskPricePerMo, + BootTime: bootTime, + Stoppable: item.Stoppable, + Rebootable: item.Rebootable, + FlexPorts: item.CanModifyFirewallRules, + PricePerHour: price, + Manufacturer: "cpu", }) continue } @@ -764,27 +777,29 @@ func ProcessInstances(items []InstanceType) []GPUInstanceInfo { capability := getGPUCapability(gpu.Name) instances = append(instances, GPUInstanceInfo{ - Type: item.Type, - Cloud: extractCloud(item.Type, item.Provider), - Provider: item.Provider, - GPUName: gpu.Name, - GPUCount: gpu.Count, - VRAMPerGPU: vramPerGPU, - TotalVRAM: totalVRAM, - Capability: capability, - VCPUs: item.VCPU, - Memory: item.Memory, - RAMInGB: ramInGB, - Arch: arch, - DiskMin: diskMin, - DiskMax: diskMax, - DiskPricePerMo: diskPricePerMo, - BootTime: bootTime, - Stoppable: item.Stoppable, - Rebootable: item.Rebootable, - FlexPorts: item.CanModifyFirewallRules, - PricePerHour: price, - Manufacturer: gpu.Manufacturer, + Type: item.Type, + Cloud: extractCloud(item.Type, item.Provider), + Provider: item.Provider, + GPUName: gpu.Name, + GPUCount: gpu.Count, + VRAMPerGPU: vramPerGPU, + TotalVRAM: totalVRAM, + Capability: capability, + VCPUs: item.VCPU, + Memory: item.Memory, + RAMInGB: ramInGB, + Arch: arch, + Region: item.Location, + AvailableRegions: item.AvailableLocations, + DiskMin: diskMin, + DiskMax: diskMax, + DiskPricePerMo: diskPricePerMo, + BootTime: bootTime, + Stoppable: item.Stoppable, + Rebootable: item.Rebootable, + FlexPorts: item.CanModifyFirewallRules, + PricePerHour: price, + Manufacturer: gpu.Manufacturer, }) } } @@ -795,6 +810,7 @@ func ProcessInstances(items []InstanceType) []GPUInstanceInfo { // FilterOptions holds all filter criteria for instances type FilterOptions struct { GPUName string + Region string Provider string Arch string MinVRAM float64 @@ -809,7 +825,7 @@ type FilterOptions struct { FlexPorts bool } -// matchesStringFilters checks GPU name and provider filters +// matchesStringFilters checks GPU name, region, provider, and architecture filters func (f *FilterOptions) matchesStringFilters(inst GPUInstanceInfo) bool { // Allow CPU-only instances through; filter out non-NVIDIA GPUs (AMD, Intel/Habana, etc.) if inst.Manufacturer != "cpu" && !strings.Contains(strings.ToUpper(inst.Manufacturer), "NVIDIA") { @@ -819,6 +835,10 @@ func (f *FilterOptions) matchesStringFilters(inst GPUInstanceInfo) bool { if f.GPUName != "" && !strings.Contains(strings.ToLower(inst.GPUName), strings.ToLower(f.GPUName)) { return false } + // Filter by region (case-insensitive partial match against primary location and available locations) + if f.Region != "" && !matchesRegion(f.Region, inst) { + return false + } // Filter by provider (case-insensitive partial match) if f.Provider != "" && !strings.Contains(strings.ToLower(inst.Provider), strings.ToLower(f.Provider)) { return false @@ -830,6 +850,22 @@ func (f *FilterOptions) matchesStringFilters(inst GPUInstanceInfo) bool { return true } +// matchesRegion checks if an instance is available in the given region +func matchesRegion(region string, inst GPUInstanceInfo) bool { + regionLower := strings.ToLower(region) + // Check primary location + if strings.Contains(strings.ToLower(inst.Region), regionLower) { + return true + } + // Check available locations + for _, loc := range inst.AvailableRegions { + if strings.Contains(strings.ToLower(loc), regionLower) { + return true + } + } + return false +} + // matchesNumericFilters checks VRAM, capability, disk, vCPU, and boot time filters func (f *FilterOptions) matchesNumericFilters(inst GPUInstanceInfo) bool { if f.MinVCPU > 0 && inst.VCPUs < f.MinVCPU { @@ -879,9 +915,10 @@ func (f *FilterOptions) matchesFilter(inst GPUInstanceInfo) bool { } // FilterInstances applies all filters to the instance list. When gpuOnly is true, CPU-only instances are excluded. -func FilterInstances(instances []GPUInstanceInfo, gpuName, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts, gpuOnly bool) []GPUInstanceInfo { +func FilterInstances(instances []GPUInstanceInfo, gpuName, region, provider, arch string, minVRAM, minTotalVRAM, minCapability, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts, gpuOnly bool) []GPUInstanceInfo { opts := &FilterOptions{ GPUName: gpuName, + Region: region, Provider: provider, Arch: arch, MinVRAM: minVRAM, @@ -909,7 +946,7 @@ func FilterInstances(instances []GPUInstanceInfo, gpuName, provider, arch string } // FilterCPUInstances filters to CPU-only instances using shared filter logic -func FilterCPUInstances(instances []GPUInstanceInfo, provider, arch string, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool) []GPUInstanceInfo { +func FilterCPUInstances(instances []GPUInstanceInfo, region, provider, arch string, minRAM, minDisk float64, minVCPU, maxBootTime int, stoppable, rebootable, flexPorts bool) []GPUInstanceInfo { // Filter out GPU instances first, then apply shared filters var cpuOnly []GPUInstanceInfo for _, inst := range instances { @@ -917,7 +954,7 @@ func FilterCPUInstances(instances []GPUInstanceInfo, provider, arch string, minR cpuOnly = append(cpuOnly, inst) } } - return FilterInstances(cpuOnly, "", provider, arch, 0, 0, 0, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false) + return FilterInstances(cpuOnly, "", region, provider, arch, 0, 0, 0, minRAM, minDisk, minVCPU, maxBootTime, stoppable, rebootable, flexPorts, false) } // SortInstances sorts the instance list by the specified column diff --git a/pkg/cmd/gpusearch/gpusearch_test.go b/pkg/cmd/gpusearch/gpusearch_test.go index d9972d68d..680413989 100644 --- a/pkg/cmd/gpusearch/gpusearch_test.go +++ b/pkg/cmd/gpusearch/gpusearch_test.go @@ -168,19 +168,19 @@ func TestFilterInstancesByGPUName(t *testing.T) { instances := ProcessInstances(response.Items) // Filter by A10G - filtered := FilterInstances(instances, "A10G", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "A10G", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 2, "Should have 2 A10G instances") // Filter by V100 - filtered = FilterInstances(instances, "V100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "V100", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 2, "Should have 2 V100 instances") // Filter by lowercase (case-insensitive) - filtered = FilterInstances(instances, "v100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "v100", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 2, "Should have 2 V100 instances (case-insensitive)") // Filter by partial match - filtered = FilterInstances(instances, "A1", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "A1", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 3, "Should have 3 instances matching 'A1' (A10G and A100)") } @@ -189,11 +189,11 @@ func TestFilterInstancesByMinVRAM(t *testing.T) { instances := ProcessInstances(response.Items) // Filter by min VRAM 24GB - filtered := FilterInstances(instances, "", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "", "", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 4, "Should have 4 instances with >= 24GB VRAM") // Filter by min VRAM 40GB - filtered = FilterInstances(instances, "", "", "", 40, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "", "", "", "", 40, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 1, "Should have 1 instance with >= 40GB VRAM") assert.Equal(t, "A100", filtered[0].GPUName) } @@ -203,11 +203,11 @@ func TestFilterInstancesByMinTotalVRAM(t *testing.T) { instances := ProcessInstances(response.Items) // Filter by min total VRAM 60GB - filtered := FilterInstances(instances, "", "", "", 0, 60, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "", "", "", "", 0, 60, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 2, "Should have 2 instances with >= 60GB total VRAM") // Filter by min total VRAM 300GB - filtered = FilterInstances(instances, "", "", "", 0, 300, 0, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "", "", "", "", 0, 300, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 1, "Should have 1 instance with >= 300GB total VRAM") assert.Equal(t, "p4d.24xlarge", filtered[0].Type) } @@ -217,11 +217,11 @@ func TestFilterInstancesByMinCapability(t *testing.T) { instances := ProcessInstances(response.Items) // Filter by capability >= 8.0 - filtered := FilterInstances(instances, "", "", "", 0, 0, 8.0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "", "", "", "", 0, 0, 8.0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 4, "Should have 4 instances with capability >= 8.0") // Filter by capability >= 8.5 - filtered = FilterInstances(instances, "", "", "", 0, 0, 8.5, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "", "", "", "", 0, 0, 8.5, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 3, "Should have 3 instances with capability >= 8.5") } @@ -230,11 +230,11 @@ func TestFilterInstancesCombined(t *testing.T) { instances := ProcessInstances(response.Items) // Filter by GPU name and min VRAM - filtered := FilterInstances(instances, "A10G", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "A10G", "", "", "", 24, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 2, "Should have 2 A10G instances with >= 24GB VRAM") // Filter by GPU name, min VRAM, and capability - filtered = FilterInstances(instances, "", "", "", 24, 0, 8.5, 0, 0, 0, 0, false, false, false, true) + filtered = FilterInstances(instances, "", "", "", "", 24, 0, 8.5, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 3, "Should have 3 instances with >= 24GB VRAM and capability >= 8.5") } @@ -336,7 +336,7 @@ func TestEmptyInstanceTypes(t *testing.T) { assert.Len(t, instances, 0, "Should have 0 instances") - filtered := FilterInstances(instances, "A100", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "A100", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 0, "Filtered should also be empty") } @@ -395,12 +395,12 @@ func TestNonGPUInstancesFilteredByDefault(t *testing.T) { instances := ProcessInstances(response.Items) // gpuOnly=true should filter out CPU instances - filtered := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + filtered := FilterInstances(instances, "", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, filtered, 1, "gpuOnly should exclude CPU instances") assert.Equal(t, "g5.xlarge", filtered[0].Type) // gpuOnly=false should keep CPU instances - filtered = FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, false) + filtered = FilterInstances(instances, "", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, false) assert.Len(t, filtered, 2, "Without gpuOnly, both CPU and GPU instances pass") } @@ -464,7 +464,7 @@ func TestFilterByMaxBootTimeExcludesUnknown(t *testing.T) { assert.Len(t, instances, 3, "Should have 3 instances before filtering") // Filter by max boot time of 10 minutes - should exclude unknown and slow-boot - filtered := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 10, false, false, false, true) + filtered := FilterInstances(instances, "", "", "", "", 0, 0, 0, 0, 0, 0, 10, false, false, false, true) assert.Len(t, filtered, 1, "Should have 1 instance with boot time <= 10 minutes") assert.Equal(t, "fast-boot", filtered[0].Type, "Only fast-boot should match") @@ -475,7 +475,7 @@ func TestFilterByMaxBootTimeExcludesUnknown(t *testing.T) { } // Without filter, all instances should be included - noFilter := FilterInstances(instances, "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) + noFilter := FilterInstances(instances, "", "", "", "", 0, 0, 0, 0, 0, 0, 0, false, false, false, true) assert.Len(t, noFilter, 3, "Without filter, all 3 instances should be included") }