From cd0e7a097413d848126e15e35b27ff6f4362a1b9 Mon Sep 17 00:00:00 2001 From: Alec Fong Date: Thu, 18 Sep 2025 11:54:12 -0700 Subject: [PATCH 1/3] add estimated time for shadeform --- v1/providers/shadeform/instancetype.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/v1/providers/shadeform/instancetype.go b/v1/providers/shadeform/instancetype.go index 350a2a98..892cb1da 100644 --- a/v1/providers/shadeform/instancetype.go +++ b/v1/providers/shadeform/instancetype.go @@ -186,6 +186,23 @@ func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeform cloud := shadeformCloud(shadeformInstanceType.Cloud) architecture := shadeformArchitecture(gpuName) + var estimatedDeployTime *time.Duration + if shadeformInstanceType.BootTime != nil { + minSec := shadeformInstanceType.BootTime.MinBootInSec + maxSec := shadeformInstanceType.BootTime.MaxBootInSec + if minSec != nil && maxSec != nil { //nolint:gocritic // if else fine + avg := (*minSec + *maxSec) / 2 + avgDuration := time.Duration(avg) * time.Second + estimatedDeployTime = &avgDuration + } else if minSec != nil { + d := time.Duration(*minSec) * time.Second + estimatedDeployTime = &d + } else if maxSec != nil { + d := time.Duration(*maxSec) * time.Second + estimatedDeployTime = &d + } + } + for _, region := range shadeformInstanceType.Availability { instanceTypes = append(instanceTypes, v1.InstanceType{ ID: v1.InstanceTypeID(c.getInstanceTypeID(instanceType, region.Region)), @@ -216,6 +233,7 @@ func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeform Location: region.Region, Provider: CloudProviderID, Cloud: cloud, + EstimatedDeployTime: estimatedDeployTime, }) } From 70179c7764f57c3887fa85726369b001eae2821a Mon Sep 17 00:00:00 2001 From: Alec Fong Date: Mon, 22 Sep 2025 17:23:31 -0700 Subject: [PATCH 2/3] add tests --- v1/providers/shadeform/instancetype.go | 37 ++++--- v1/providers/shadeform/instancetype_test.go | 108 ++++++++++++++++++++ 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/v1/providers/shadeform/instancetype.go b/v1/providers/shadeform/instancetype.go index 892cb1da..f93672bd 100644 --- a/v1/providers/shadeform/instancetype.go +++ b/v1/providers/shadeform/instancetype.go @@ -170,22 +170,7 @@ func (c *ShadeformClient) getShadeformCloudAndInstanceType(instanceType string) return shadeformCloud, shadeformInstanceType, nil } -// convertShadeformInstanceTypeToV1InstanceTypes - converts a shadeform returned instance type to a specific instance type and region of availability -func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeformInstanceType openapi.InstanceType) ([]v1.InstanceType, error) { - instanceType := c.getInstanceType(string(shadeformInstanceType.Cloud), shadeformInstanceType.ShadeInstanceType) - - instanceTypes := []v1.InstanceType{} - - basePrice, err := convertHourlyPriceToAmount(shadeformInstanceType.HourlyPrice) - if err != nil { - return nil, err - } - - gpuName := shadeformGPUTypeToBrevGPUName(shadeformInstanceType.Configuration.GpuType) - gpuManufacturer := v1.GetManufacturer(shadeformInstanceType.Configuration.GpuManufacturer) - cloud := shadeformCloud(shadeformInstanceType.Cloud) - architecture := shadeformArchitecture(gpuName) - +func (c *ShadeformClient) getEstimatedDeployTime(shadeformInstanceType openapi.InstanceType) *time.Duration { var estimatedDeployTime *time.Duration if shadeformInstanceType.BootTime != nil { minSec := shadeformInstanceType.BootTime.MinBootInSec @@ -202,6 +187,26 @@ func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeform estimatedDeployTime = &d } } + return estimatedDeployTime +} + +// convertShadeformInstanceTypeToV1InstanceTypes - converts a shadeform returned instance type to a specific instance type and region of availability +func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeformInstanceType openapi.InstanceType) ([]v1.InstanceType, error) { + instanceType := c.getInstanceType(string(shadeformInstanceType.Cloud), shadeformInstanceType.ShadeInstanceType) + + instanceTypes := []v1.InstanceType{} + + basePrice, err := convertHourlyPriceToAmount(shadeformInstanceType.HourlyPrice) + if err != nil { + return nil, err + } + + gpuName := shadeformGPUTypeToBrevGPUName(shadeformInstanceType.Configuration.GpuType) + gpuManufacturer := v1.GetManufacturer(shadeformInstanceType.Configuration.GpuManufacturer) + cloud := shadeformCloud(shadeformInstanceType.Cloud) + architecture := shadeformArchitecture(gpuName) + + estimatedDeployTime := c.getEstimatedDeployTime(shadeformInstanceType) for _, region := range shadeformInstanceType.Availability { instanceTypes = append(instanceTypes, v1.InstanceType{ diff --git a/v1/providers/shadeform/instancetype_test.go b/v1/providers/shadeform/instancetype_test.go index fc4c15ed..27e074b1 100644 --- a/v1/providers/shadeform/instancetype_test.go +++ b/v1/providers/shadeform/instancetype_test.go @@ -2,8 +2,10 @@ package v1 import ( "testing" + "time" v1 "github.com/brevdev/cloud/v1" + openapi "github.com/brevdev/cloud/v1/providers/shadeform/gen/shadeform" "github.com/stretchr/testify/assert" ) @@ -95,3 +97,109 @@ func TestIsSelectedByArgs(t *testing.T) { }) } } + +func TestGetEstimatedDeployTime(t *testing.T) { + t.Parallel() + + // Helper function to create int32 pointers + int32Ptr := func(v int32) *int32 { + return &v + } + + // Helper function to create time.Duration pointers + durationPtr := func(d time.Duration) *time.Duration { + return &d + } + + cases := []struct { + name string + shadeformInstanceType openapi.InstanceType + expectedEstimatedDeploy *time.Duration + }{ + { + name: "both min and max boot times provided - should return average", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: int32Ptr(60), // 1 minute + MaxBootInSec: int32Ptr(180), // 3 minutes + }, + }, + expectedEstimatedDeploy: durationPtr(120 * time.Second), // 2 minutes average + }, + { + name: "only min boot time provided - should return min", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: int32Ptr(90), // 1.5 minutes + MaxBootInSec: nil, + }, + }, + expectedEstimatedDeploy: durationPtr(90 * time.Second), + }, + { + name: "only max boot time provided - should return max", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: nil, + MaxBootInSec: int32Ptr(300), // 5 minutes + }, + }, + expectedEstimatedDeploy: durationPtr(300 * time.Second), + }, + { + name: "boot time with both values nil - should return nil", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: nil, + MaxBootInSec: nil, + }, + }, + expectedEstimatedDeploy: nil, + }, + { + name: "no boot time provided - should return nil", + shadeformInstanceType: openapi.InstanceType{ + BootTime: nil, + }, + expectedEstimatedDeploy: nil, + }, + { + name: "zero values for min and max - should return zero duration", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: int32Ptr(0), + MaxBootInSec: int32Ptr(0), + }, + }, + expectedEstimatedDeploy: durationPtr(0 * time.Second), + }, + { + name: "large values for min and max - should handle correctly", + shadeformInstanceType: openapi.InstanceType{ + BootTime: &openapi.BootTime{ + MinBootInSec: int32Ptr(3600), // 1 hour + MaxBootInSec: int32Ptr(7200), // 2 hours + }, + }, + expectedEstimatedDeploy: durationPtr(5400 * time.Second), // 1.5 hours average + }, + } + + // Create a client instance to test the method + client := &ShadeformClient{} + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + result := client.getEstimatedDeployTime(tt.shadeformInstanceType) + + if tt.expectedEstimatedDeploy == nil { + assert.Nil(t, result) + } else { + assert.NotNil(t, result) + assert.Equal(t, *tt.expectedEstimatedDeploy, *result) + } + }) + } +} From 37fe538c59a4879f567ea3c33f2344a2756fcd35 Mon Sep 17 00:00:00 2001 From: Alec Fong Date: Wed, 24 Sep 2025 18:17:01 -0700 Subject: [PATCH 3/3] flatten --- v1/providers/shadeform/instancetype.go | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/v1/providers/shadeform/instancetype.go b/v1/providers/shadeform/instancetype.go index f93672bd..37d75221 100644 --- a/v1/providers/shadeform/instancetype.go +++ b/v1/providers/shadeform/instancetype.go @@ -171,21 +171,25 @@ func (c *ShadeformClient) getShadeformCloudAndInstanceType(instanceType string) } func (c *ShadeformClient) getEstimatedDeployTime(shadeformInstanceType openapi.InstanceType) *time.Duration { + bootTime := shadeformInstanceType.BootTime + if bootTime == nil { + return nil + } + + minSec := bootTime.MinBootInSec + maxSec := bootTime.MaxBootInSec + var estimatedDeployTime *time.Duration - if shadeformInstanceType.BootTime != nil { - minSec := shadeformInstanceType.BootTime.MinBootInSec - maxSec := shadeformInstanceType.BootTime.MaxBootInSec - if minSec != nil && maxSec != nil { //nolint:gocritic // if else fine - avg := (*minSec + *maxSec) / 2 - avgDuration := time.Duration(avg) * time.Second - estimatedDeployTime = &avgDuration - } else if minSec != nil { - d := time.Duration(*minSec) * time.Second - estimatedDeployTime = &d - } else if maxSec != nil { - d := time.Duration(*maxSec) * time.Second - estimatedDeployTime = &d - } + if minSec != nil && maxSec != nil { //nolint:gocritic // if else fine + avg := (*minSec + *maxSec) / 2 + avgDuration := time.Duration(avg) * time.Second + estimatedDeployTime = &avgDuration + } else if minSec != nil { + d := time.Duration(*minSec) * time.Second + estimatedDeployTime = &d + } else if maxSec != nil { + d := time.Duration(*maxSec) * time.Second + estimatedDeployTime = &d } return estimatedDeployTime }