diff --git a/v1/bytes.go b/v1/bytes.go new file mode 100644 index 0000000..f1a1895 --- /dev/null +++ b/v1/bytes.go @@ -0,0 +1,56 @@ +package v1 + +var zeroBytes = Bytes{value: 0, unit: Byte} + +// NewBytes creates a new Bytes with the given value and unit +func NewBytes(value BytesValue, unit BytesUnit) Bytes { + if value < 0 { + return zeroBytes + } + return Bytes{ + value: value, + unit: unit, + } +} + +type ( + BytesValue int64 + BytesUnit string +) + +// Bytes represents a number of some unit of bytes +type Bytes struct { + value BytesValue + unit BytesUnit +} + +// Value is the whole non-negative number of bytes of the specified unit +func (b Bytes) Value() BytesValue { + return b.value +} + +// Unit is the unit of the byte value +func (b Bytes) Unit() BytesUnit { + return b.unit +} + +// ByteUnit is a unit of measurement for bytes +const ( + Byte BytesUnit = "B" + + // Base 10 + Kilobyte BytesUnit = "KB" + Megabyte BytesUnit = "MB" + Gigabyte BytesUnit = "GB" + Terabyte BytesUnit = "TB" + Petabyte BytesUnit = "PB" + Exabyte BytesUnit = "EB" + + // Base 2 + Kibibyte BytesUnit = "KiB" + Mebibyte BytesUnit = "MiB" + Gibibyte BytesUnit = "GiB" + Tebibyte BytesUnit = "TiB" + Pebibyte BytesUnit = "PiB" + Exbibyte BytesUnit = "EiB" +) diff --git a/v1/instance.go b/v1/instance.go index bf769cf..772f331 100644 --- a/v1/instance.go +++ b/v1/instance.go @@ -195,7 +195,8 @@ type Instance struct { Hostname string ImageID string InstanceType string - DiskSize units.Base2Bytes + DiskSize units.Base2Bytes // TODO: deprecate in favor of DiskSizeByteValue + DiskSizeBytes Bytes VolumeType string PubKeyFingerprint string SSHUser string @@ -274,7 +275,8 @@ type CreateInstanceAttrs struct { ImageID string InstanceType string UserDataBase64 string - DiskSize units.Base2Bytes + DiskSize units.Base2Bytes // TODO: deprecate in favor of DiskSizeByteValue + DiskSizeBytes Bytes Tags Tags FirewallRules FirewallRules UseSpot bool diff --git a/v1/instancetype.go b/v1/instancetype.go index 3cf6e82..3c09440 100644 --- a/v1/instancetype.go +++ b/v1/instancetype.go @@ -64,7 +64,8 @@ type InstanceType struct { SupportedStorage []Storage ElasticRootVolume bool SupportedUsageClasses []string - Memory units.Base2Bytes + Memory units.Base2Bytes // TODO: deprecate in favor of MemoryByteValue + MemoryBytes Bytes MaximumNetworkInterfaces int32 NetworkPerformance string SupportedNumCores []int32 @@ -114,7 +115,8 @@ func MakeGenericInstanceTypeIDFromInstance(instance Instance) InstanceTypeID { type GPU struct { Count int32 - Memory units.Base2Bytes + Memory units.Base2Bytes // TODO: deprecate in favor of MemoryByteValue + MemoryBytes Bytes MemoryDetails string // "", "HBM", "GDDR", "DDR", etc. NetworkDetails string // "PCIe", "SXM4", "SXM5", etc. Manufacturer Manufacturer @@ -378,7 +380,7 @@ func normalizeInstanceTypes(types []InstanceType) []InstanceType { // ValidateStableInstanceTypeIDs validates that the provided stable instance type IDs are valid and stable // This function ensures that stable IDs exist in the current instance types and have required properties -func ValidateStableInstanceTypeIDs(ctx context.Context, client CloudInstanceType, stableIDs []InstanceTypeID) error { +func ValidateStableInstanceTypeIDs(ctx context.Context, client CloudInstanceType, stableIDs []InstanceTypeID) error { //nolint:gocyclo // test // Get all instance types allTypes, err := client.GetInstanceTypes(ctx, GetInstanceTypeArgs{}) if err != nil { @@ -427,7 +429,7 @@ func ValidateStableInstanceTypeIDs(ctx context.Context, client CloudInstanceType // Check that supported storage has price information for i, storage := range instanceType.SupportedStorage { - if storage.MinSize != nil { + if storage.MinSize != nil || storage.MinSizeBytes != nil { if storage.PricePerGBHr == nil { return fmt.Errorf("instance type %s should have storage %d price", instanceType.ID, i) } diff --git a/v1/providers/fluidstack/instancetype.go b/v1/providers/fluidstack/instancetype.go index 4f04bce..3b08d9e 100644 --- a/v1/providers/fluidstack/instancetype.go +++ b/v1/providers/fluidstack/instancetype.go @@ -9,7 +9,7 @@ import ( "github.com/alecthomas/units" "github.com/bojanz/currency" - "github.com/brevdev/cloud/v1" + v1 "github.com/brevdev/cloud/v1" openapi "github.com/brevdev/cloud/v1/providers/fluidstack/gen/fluidstack" ) @@ -82,12 +82,14 @@ func convertFluidStackInstanceTypeToV1InstanceType(location string, fsInstanceTy } } + var memoryBytes v1.Bytes var ram units.Base2Bytes if fsInstanceType.Memory != "" { memoryStr := strings.TrimSuffix(fsInstanceType.Memory, "GB") memoryStr = strings.TrimSpace(memoryStr) if memoryGB, err := strconv.ParseFloat(memoryStr, 64); err == nil { ram = units.Base2Bytes(memoryGB) * units.Gibibyte + memoryBytes = v1.NewBytes(v1.BytesValue(memoryGB), v1.Gigabyte) } } @@ -102,6 +104,7 @@ func convertFluidStackInstanceTypeToV1InstanceType(location string, fsInstanceTy Type: fsInstanceType.Name, VCPU: vcpus, Memory: ram, + MemoryBytes: memoryBytes, SupportedGPUs: gpus, BasePrice: &price, IsAvailable: isAvailable, diff --git a/v1/providers/lambdalabs/instance.go b/v1/providers/lambdalabs/instance.go index 6b153eb..706d882 100644 --- a/v1/providers/lambdalabs/instance.go +++ b/v1/providers/lambdalabs/instance.go @@ -177,9 +177,10 @@ func convertLambdaLabsInstanceToV1Instance(instance openapi.Instance) *v1.Instan Status: v1.Status{ LifecycleStatus: convertLambdaLabsStatusToV1Status(instance.Status), }, - InstanceType: instance.InstanceType.Name, - VolumeType: "ssd", - DiskSize: units.GiB * units.Base2Bytes(instance.InstanceType.Specs.StorageGib), + InstanceType: instance.InstanceType.Name, + VolumeType: "ssd", + DiskSize: units.GiB * units.Base2Bytes(instance.InstanceType.Specs.StorageGib), + DiskSizeBytes: v1.NewBytes(v1.BytesValue(instance.InstanceType.Specs.StorageGib), v1.Gibibyte), FirewallRules: v1.FirewallRules{ IngressRules: []v1.FirewallRule{generateFirewallRouteFromPort(22), generateFirewallRouteFromPort(2222)}, // TODO pull from api EgressRules: []v1.FirewallRule{generateFirewallRouteFromPort(22), generateFirewallRouteFromPort(2222)}, // TODO pull from api diff --git a/v1/providers/lambdalabs/instancetype.go b/v1/providers/lambdalabs/instancetype.go index 6b79fbb..2f91c6d 100644 --- a/v1/providers/lambdalabs/instancetype.go +++ b/v1/providers/lambdalabs/instancetype.go @@ -3,6 +3,7 @@ package v1 import ( "context" "fmt" + "math" "regexp" "strconv" "strings" @@ -127,7 +128,11 @@ func parseGPUFromDescription(input string) (v1.GPU, error) { } memoryStr := memoryMatch[1] memoryGiB, _ := strconv.Atoi(memoryStr) + if memoryGiB > math.MaxInt32 { + memoryGiB = math.MaxInt32 + } gpu.Memory = units.GiB * units.Base2Bytes(memoryGiB) + gpu.MemoryBytes = v1.NewBytes(v1.BytesValue(memoryGiB), v1.Gibibyte) // Extract the network details networkRegex := regexp.MustCompile(`(\w+\s?)+\)`) @@ -172,19 +177,22 @@ func convertLambdaLabsInstanceTypeToV1InstanceType(location string, instType ope if err != nil { return v1.InstanceType{}, err } + it := v1.InstanceType{ Location: location, Type: instType.Name, SupportedGPUs: gpus, SupportedStorage: []v1.Storage{ { - Type: "ssd", - Count: 1, - Size: units.GiB * units.Base2Bytes(instType.Specs.StorageGib), + Type: "ssd", + Count: 1, + Size: units.GiB * units.Base2Bytes(instType.Specs.StorageGib), + SizeBytes: v1.NewBytes(v1.BytesValue(instType.Specs.StorageGib), v1.Gibibyte), }, }, SupportedUsageClasses: []string{"on-demand"}, Memory: units.GiB * units.Base2Bytes(instType.Specs.MemoryGib), + MemoryBytes: v1.NewBytes(v1.BytesValue(instType.Specs.MemoryGib), v1.Gibibyte), MaximumNetworkInterfaces: 0, NetworkPerformance: "", SupportedNumCores: []int32{}, diff --git a/v1/providers/launchpad/instance_get.go b/v1/providers/launchpad/instance_get.go index fa190da..fbd9d3e 100644 --- a/v1/providers/launchpad/instance_get.go +++ b/v1/providers/launchpad/instance_get.go @@ -44,20 +44,18 @@ func launchpadDeploymentToInstance(deployment *openapi.Deployment) (v1.Instance, return v1.Instance{}, errors.WrapAndTrace(err) } - var diskSize units.Base2Bytes + var totalStorageSize int32 nodes := deployment.GetCluster().Cluster.GetNodes() if len(nodes) == 0 { - diskSize = 0 + totalStorageSize = 0 } else { node := nodes[0] // Calculate disk size storage := node.Node.GetStorage() - size := 0 for _, s := range storage { - size += int(s.GetSize()) + totalStorageSize += s.GetSize() } - diskSize = units.Base2Bytes(size) * units.GiB } inst := v1.Instance{ @@ -78,10 +76,11 @@ func launchpadDeploymentToInstance(deployment *openapi.Deployment) (v1.Instance, ToPort: 2022, }, }, - DiskSize: diskSize, - Location: deployment.GetRegion(), - PublicDNS: deployment.GetCluster().Cluster.GetPublicAddress(), - PublicIP: deployment.GetCluster().Cluster.GetPublicAddress(), + DiskSize: units.Base2Bytes(totalStorageSize) * units.GiB, + DiskSizeBytes: v1.NewBytes(v1.BytesValue(totalStorageSize), v1.Gigabyte), + Location: deployment.GetRegion(), + PublicDNS: deployment.GetCluster().Cluster.GetPublicAddress(), + PublicIP: deployment.GetCluster().Cluster.GetPublicAddress(), } cluster := deployment.GetCluster().Cluster diff --git a/v1/providers/launchpad/instancetype.go b/v1/providers/launchpad/instancetype.go index 22c5826..87d16bf 100644 --- a/v1/providers/launchpad/instancetype.go +++ b/v1/providers/launchpad/instancetype.go @@ -204,6 +204,7 @@ func launchpadInstanceTypeToInstanceType(launchpadInstanceType openapi.InstanceT Type: typeName, VCPU: launchpadInstanceType.Cpu, Memory: gbToBytes(launchpadInstanceType.MemoryGb), + MemoryBytes: v1.NewBytes(v1.BytesValue(launchpadInstanceType.MemoryGb), v1.Gigabyte), SupportedGPUs: []v1.GPU{gpu}, SupportedStorage: storage, SupportedArchitectures: []v1.Architecture{launchpadArchitectureToArchitecture(launchpadInstanceType.SystemArch)}, @@ -242,9 +243,10 @@ func launchpadStorageToStorages(launchpadStorage []openapi.InstanceTypeStorage) storage := make([]v1.Storage, len(launchpadStorage)) for i, s := range launchpadStorage { storage[i] = v1.Storage{ - Count: 1, - Size: gbToBytes(s.SizeGb), - Type: string(s.Type), + Count: 1, + Size: gbToBytes(s.SizeGb), + SizeBytes: v1.NewBytes(v1.BytesValue(s.SizeGb), v1.Gigabyte), + Type: string(s.Type), } } return storage @@ -261,6 +263,7 @@ func launchpadGpusToGpus(lpGpus []openapi.InstanceTypeGpu) []v1.GPU { Manufacturer: v1.GetManufacturer(gp.Manufacturer), Count: gp.Count, Memory: gbToBytes(gp.MemoryGb), + MemoryBytes: v1.NewBytes(v1.BytesValue(int64(gp.MemoryGb)), v1.Gigabyte), NetworkDetails: string(gp.InterconnectionType), Type: strings.ToUpper(gp.Model), } @@ -307,8 +310,10 @@ func launchpadClusterToInstanceType(cluster openapi.Cluster) *v1.InstanceType { vcpu = *node.Cpu } var memory units.Base2Bytes + var memoryBytes v1.Bytes if node.Memory != nil { memory = gbToBytes(*node.Memory) + memoryBytes = v1.NewBytes(v1.BytesValue(*node.Memory), v1.Gigabyte) } isAvailable := (cluster.ProvisioningState != nil && *cluster.ProvisioningState == openapi.ProvisioningStateReady) @@ -328,6 +333,7 @@ func launchpadClusterToInstanceType(cluster openapi.Cluster) *v1.InstanceType { SupportedGPUs: []v1.GPU{*gpu}, SupportedStorage: storage, Memory: memory, + MemoryBytes: memoryBytes, VCPU: vcpu, IsAvailable: isAvailable, Location: location, @@ -359,8 +365,10 @@ func launchpadGputoGpu(node openapi.Node) *v1.GPU { } var lpGpuMemory units.Base2Bytes + var lpGpuMemoryBytes v1.Bytes if lpGpu.Memory != nil { lpGpuMemory = gbToBytes(*lpGpu.Memory) + lpGpuMemoryBytes = v1.NewBytes(v1.BytesValue(*lpGpu.Memory), v1.Gigabyte) } gpu := &v1.GPU{ @@ -368,6 +376,7 @@ func launchpadGputoGpu(node openapi.Node) *v1.GPU { Count: lpGpuCount, NetworkDetails: lpGpuFormFactor, Memory: lpGpuMemory, + MemoryBytes: lpGpuMemoryBytes, Manufacturer: "NVIDIA", // The only supported manufacturer for Launchpad } return gpu diff --git a/v1/providers/shadeform/instance.go b/v1/providers/shadeform/instance.go index 8175cd6..1867ffd 100644 --- a/v1/providers/shadeform/instance.go +++ b/v1/providers/shadeform/instance.go @@ -295,6 +295,7 @@ func (c *ShadeformClient) convertInstanceInfoResponseToV1Instance(ctx context.Co InstanceType: instanceType, InstanceTypeID: v1.InstanceTypeID(c.getInstanceTypeID(instanceType, instanceInfo.Region)), DiskSize: diskSize, + DiskSizeBytes: v1.NewBytes(v1.BytesValue(instanceInfo.Configuration.StorageInGb), v1.Gigabyte), SSHUser: instanceInfo.SshUser, SSHPort: int(instanceInfo.SshPort), Status: v1.Status{ diff --git a/v1/providers/shadeform/instancetype.go b/v1/providers/shadeform/instancetype.go index 7be4ae6..8bd2e2e 100644 --- a/v1/providers/shadeform/instancetype.go +++ b/v1/providers/shadeform/instancetype.go @@ -225,14 +225,16 @@ func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeform for _, region := range shadeformInstanceType.Availability { instanceTypes = append(instanceTypes, v1.InstanceType{ - ID: v1.InstanceTypeID(c.getInstanceTypeID(instanceType, region.Region)), - Type: instanceType, - VCPU: shadeformInstanceType.Configuration.Vcpus, - Memory: units.Base2Bytes(shadeformInstanceType.Configuration.MemoryInGb) * units.GiB, + ID: v1.InstanceTypeID(c.getInstanceTypeID(instanceType, region.Region)), + Type: instanceType, + VCPU: shadeformInstanceType.Configuration.Vcpus, + Memory: units.Base2Bytes(shadeformInstanceType.Configuration.MemoryInGb) * units.GiB, + MemoryBytes: v1.NewBytes(v1.BytesValue(shadeformInstanceType.Configuration.MemoryInGb), v1.Gigabyte), SupportedGPUs: []v1.GPU{ { Count: shadeformInstanceType.Configuration.NumGpus, Memory: units.Base2Bytes(shadeformInstanceType.Configuration.VramPerGpuInGb) * units.GiB, + MemoryBytes: v1.NewBytes(v1.BytesValue(shadeformInstanceType.Configuration.VramPerGpuInGb), v1.Gigabyte), MemoryDetails: "", NetworkDetails: shadeformInstanceType.Configuration.Interconnect, Manufacturer: gpuManufacturer, @@ -242,9 +244,10 @@ func (c *ShadeformClient) convertShadeformInstanceTypeToV1InstanceType(shadeform }, SupportedStorage: []v1.Storage{ // TODO: add storage (look in configuration) { - Type: "ssd", - Count: 1, - Size: units.Base2Bytes(shadeformInstanceType.Configuration.StorageInGb) * units.GiB, + Type: "ssd", + Count: 1, + Size: units.Base2Bytes(shadeformInstanceType.Configuration.StorageInGb) * units.GiB, + SizeBytes: v1.NewBytes(v1.BytesValue(shadeformInstanceType.Configuration.StorageInGb), v1.Gigabyte), }, }, SupportedArchitectures: []v1.Architecture{architecture}, diff --git a/v1/storage.go b/v1/storage.go index 05f7750..53e6a94 100644 --- a/v1/storage.go +++ b/v1/storage.go @@ -9,10 +9,13 @@ import ( type Storage struct { Count int32 - Size units.Base2Bytes + Size units.Base2Bytes // TODO: deprecate in favor of SizeByteValue + SizeBytes Bytes Type string - MinSize *units.Base2Bytes - MaxSize *units.Base2Bytes + MinSize *units.Base2Bytes // TODO: deprecate in favor of MinSizeByteValue + MinSizeBytes *Bytes + MaxSize *units.Base2Bytes // TODO: deprecate in favor of MaxSizeByteValue + MaxSizeBytes *Bytes PricePerGBHr *currency.Amount IsEphemeral bool IsAdditionalDisk bool @@ -21,7 +24,8 @@ type Storage struct { } type Disk struct { - Size units.Base2Bytes + Size units.Base2Bytes // TODO: deprecate in favor of SizeByteValue + SizeBytes Bytes Type string MountPath string } @@ -32,6 +36,7 @@ type CloudResizeInstanceVolume interface { type ResizeInstanceVolumeArgs struct { InstanceID CloudProviderInstanceID - Size units.Base2Bytes + Size units.Base2Bytes // TODO: deprecate in favor of SizeByteValue + SizeBytes Bytes WaitForOptimizing bool }