mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
fix rounding of volume storage requests
This commit is contained in:
parent
35061acc28
commit
ce7a7bbf9b
@ -214,9 +214,9 @@ func (util *portworxVolumeUtil) ResizeVolume(spec *volume.Spec, newSize resource
|
|||||||
}
|
}
|
||||||
|
|
||||||
vol := vols[0]
|
vol := vols[0]
|
||||||
tBytes, ok := newSize.AsInt64()
|
tBytes, err := volumehelpers.RoundUpToB(newSize)
|
||||||
if !ok {
|
if err != nil {
|
||||||
return fmt.Errorf("quantity %v is too great, overflows int64", newSize)
|
return err
|
||||||
}
|
}
|
||||||
newSizeInBytes := uint64(tBytes)
|
newSizeInBytes := uint64(tBytes)
|
||||||
if vol.Spec.Size >= newSizeInBytes {
|
if vol.Spec.Size >= newSizeInBytes {
|
||||||
|
@ -18,6 +18,8 @@ package helpers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
)
|
)
|
||||||
@ -41,139 +43,110 @@ const (
|
|||||||
|
|
||||||
// RoundUpToGiB rounds up given quantity upto chunks of GiB
|
// RoundUpToGiB rounds up given quantity upto chunks of GiB
|
||||||
func RoundUpToGiB(size resource.Quantity) (int64, error) {
|
func RoundUpToGiB(size resource.Quantity) (int64, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt64(size, GiB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSize(requestBytes, GiB), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToMB rounds up given quantity to chunks of MB
|
// RoundUpToMB rounds up given quantity to chunks of MB
|
||||||
func RoundUpToMB(size resource.Quantity) (int64, error) {
|
func RoundUpToMB(size resource.Quantity) (int64, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt64(size, MB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSize(requestBytes, MB), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToMiB rounds up given quantity upto chunks of MiB
|
// RoundUpToMiB rounds up given quantity upto chunks of MiB
|
||||||
func RoundUpToMiB(size resource.Quantity) (int64, error) {
|
func RoundUpToMiB(size resource.Quantity) (int64, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt64(size, MiB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSize(requestBytes, MiB), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToKB rounds up given quantity to chunks of KB
|
// RoundUpToKB rounds up given quantity to chunks of KB
|
||||||
func RoundUpToKB(size resource.Quantity) (int64, error) {
|
func RoundUpToKB(size resource.Quantity) (int64, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt64(size, KB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSize(requestBytes, KB), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToKiB rounds up given quantity upto chunks of KiB
|
// RoundUpToKiB rounds up given quantity to chunks of KiB
|
||||||
func RoundUpToKiB(size resource.Quantity) (int64, error) {
|
func RoundUpToKiB(size resource.Quantity) (int64, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt64(size, KiB)
|
||||||
if !ok {
|
}
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
// RoundUpToB rounds up given quantity to chunks of bytes
|
||||||
return roundUpSize(requestBytes, KiB), nil
|
func RoundUpToB(size resource.Quantity) (int64, error) {
|
||||||
|
return roundUpSizeInt64(size, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToGiBInt rounds up given quantity upto chunks of GiB. It returns an
|
// RoundUpToGiBInt rounds up given quantity upto chunks of GiB. It returns an
|
||||||
// int instead of an int64 and an error if there's overflow
|
// int instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToGiBInt(size resource.Quantity) (int, error) {
|
func RoundUpToGiBInt(size resource.Quantity) (int, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt(size, GiB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSizeInt(requestBytes, GiB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToMBInt rounds up given quantity to chunks of MB. It returns an
|
// RoundUpToMBInt rounds up given quantity to chunks of MB. It returns an
|
||||||
// int instead of an int64 and an error if there's overflow
|
// int instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToMBInt(size resource.Quantity) (int, error) {
|
func RoundUpToMBInt(size resource.Quantity) (int, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt(size, MB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSizeInt(requestBytes, MB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToMiBInt rounds up given quantity upto chunks of MiB. It returns an
|
// RoundUpToMiBInt rounds up given quantity upto chunks of MiB. It returns an
|
||||||
// int instead of an int64 and an error if there's overflow
|
// int instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToMiBInt(size resource.Quantity) (int, error) {
|
func RoundUpToMiBInt(size resource.Quantity) (int, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt(size, MiB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSizeInt(requestBytes, MiB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToKBInt rounds up given quantity to chunks of KB. It returns an
|
// RoundUpToKBInt rounds up given quantity to chunks of KB. It returns an
|
||||||
// int instead of an int64 and an error if there's overflow
|
// int instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToKBInt(size resource.Quantity) (int, error) {
|
func RoundUpToKBInt(size resource.Quantity) (int, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt(size, KB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSizeInt(requestBytes, KB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToKiBInt rounds up given quantity upto chunks of KiB. It returns an
|
// RoundUpToKiBInt rounds up given quantity upto chunks of KiB. It returns an
|
||||||
// int instead of an int64 and an error if there's overflow
|
// int instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToKiBInt(size resource.Quantity) (int, error) {
|
func RoundUpToKiBInt(size resource.Quantity) (int, error) {
|
||||||
requestBytes := size.Value()
|
return roundUpSizeInt(size, KiB)
|
||||||
return roundUpSizeInt(requestBytes, KiB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundUpToGiBInt32 rounds up given quantity up to chunks of GiB. It returns an
|
// RoundUpToGiBInt32 rounds up given quantity up to chunks of GiB. It returns an
|
||||||
// int32 instead of an int64 and an error if there's overflow
|
// int32 instead of an int64 and an error if there's overflow
|
||||||
func RoundUpToGiBInt32(size resource.Quantity) (int32, error) {
|
func RoundUpToGiBInt32(size resource.Quantity) (int32, error) {
|
||||||
requestBytes, ok := size.AsInt64()
|
return roundUpSizeInt32(size, GiB)
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
|
||||||
}
|
|
||||||
return roundUpSizeInt32(requestBytes, GiB)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundUpSizeInt calculates how many allocation units are needed to accommodate
|
// roundUpSizeInt calculates how many allocation units are needed to accommodate
|
||||||
// a volume of given size. It returns an int instead of an int64 and an error if
|
// a volume of a given size. It returns an int and an error if there's overflow
|
||||||
// there's overflow
|
func roundUpSizeInt(size resource.Quantity, allocationUnitBytes int64) (int, error) {
|
||||||
func roundUpSizeInt(volumeSizeBytes int64, allocationUnitBytes int64) (int, error) {
|
if bits.UintSize == 32 {
|
||||||
roundedUp := roundUpSize(volumeSizeBytes, allocationUnitBytes)
|
res, err := roundUpSizeInt32(size, allocationUnitBytes)
|
||||||
roundedUpInt := int(roundedUp)
|
return int(res), err
|
||||||
if int64(roundedUpInt) != roundedUp {
|
|
||||||
return 0, fmt.Errorf("capacity %v is too great, casting results in integer overflow", roundedUp)
|
|
||||||
}
|
}
|
||||||
return roundedUpInt, nil
|
res, err := roundUpSizeInt64(size, allocationUnitBytes)
|
||||||
|
return int(res), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundUpSizeInt32 calculates how many allocation units are needed to accommodate
|
// roundUpSizeInt32 calculates how many allocation units are needed to accommodate
|
||||||
// a volume of given size. It returns an int32 instead of an int64 and an error if
|
// a volume of a given size. It returns an int32 and an error if there's overflow
|
||||||
// there's overflow
|
func roundUpSizeInt32(size resource.Quantity, allocationUnitBytes int64) (int32, error) {
|
||||||
func roundUpSizeInt32(volumeSizeBytes int64, allocationUnitBytes int64) (int32, error) {
|
roundedUpInt32, err := roundUpSizeInt64(size, allocationUnitBytes)
|
||||||
roundedUp := roundUpSize(volumeSizeBytes, allocationUnitBytes)
|
if err != nil {
|
||||||
roundedUpInt32 := int32(roundedUp)
|
return 0, err
|
||||||
if int64(roundedUpInt32) != roundedUp {
|
|
||||||
return 0, fmt.Errorf("quantity %v is too great, overflows int32", roundedUp)
|
|
||||||
}
|
}
|
||||||
return roundedUpInt32, nil
|
if roundedUpInt32 > math.MaxInt32 {
|
||||||
|
return 0, fmt.Errorf("quantity %s is too great, overflows int32", size.String())
|
||||||
|
}
|
||||||
|
return int32(roundedUpInt32), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundUpSize calculates how many allocation units are needed to accommodate
|
// roundUpSize64 calculates how many allocation units are needed to accommodate
|
||||||
// a volume of given size. E.g. when user wants 1500MiB volume, while AWS EBS
|
// a volume of a given size. E.g. when user wants 1500MiB volume, while AWS EBS
|
||||||
// allocates volumes in gibibyte-sized chunks,
|
// allocates volumes in gibibyte-sized chunks,
|
||||||
// RoundUpSize(1500 * 1024*1024, 1024*1024*1024) returns '2'
|
// roundUpSizeInt64(1500MiB, 1024*1024*1024) returns '2'
|
||||||
// (2 GiB is the smallest allocatable volume that can hold 1500MiB)
|
// (2 GiB is the smallest allocatable volume that can hold 1500MiB)
|
||||||
func roundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 {
|
// It returns an int64 and an error if the size was too great to work with
|
||||||
|
func roundUpSizeInt64(size resource.Quantity, allocationUnitBytes int64) (int64, error) {
|
||||||
|
// CmpInt64() actually returns 0 when comparing an amount bigger than MaxInt64
|
||||||
|
// with MaxInt64
|
||||||
|
if size.CmpInt64(math.MaxInt64) >= 0 {
|
||||||
|
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
|
||||||
|
}
|
||||||
|
volumeSizeBytes := size.Value()
|
||||||
roundedUp := volumeSizeBytes / allocationUnitBytes
|
roundedUp := volumeSizeBytes / allocationUnitBytes
|
||||||
if volumeSizeBytes%allocationUnitBytes > 0 {
|
if volumeSizeBytes%allocationUnitBytes > 0 {
|
||||||
roundedUp++
|
roundedUp++
|
||||||
}
|
}
|
||||||
return roundedUp
|
return roundedUp, nil
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,18 @@ func Test_RoundUpToGiB(t *testing.T) {
|
|||||||
roundedVal: int64(1000),
|
roundedVal: int64(1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int64",
|
name: "round decimal to GiB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to GiB",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(8588886016),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to GiB that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
roundedVal: int64(0),
|
roundedVal: int64(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -125,8 +135,18 @@ func Test_RoundUpToMB(t *testing.T) {
|
|||||||
roundedVal: int64(1073742),
|
roundedVal: int64(1073742),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int64",
|
name: "round decimal to MB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(1289),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to MB",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(9222246136948),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to MB that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
roundedVal: int64(0),
|
roundedVal: int64(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -190,8 +210,18 @@ func Test_RoundUpToMiB(t *testing.T) {
|
|||||||
roundedVal: int64(1024000),
|
roundedVal: int64(1024000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int64",
|
name: "round decimal to MiB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(1229),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to MiB",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(8795019280384),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to MiB that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
roundedVal: int64(0),
|
roundedVal: int64(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -255,8 +285,18 @@ func Test_RoundUpToKB(t *testing.T) {
|
|||||||
roundedVal: int64(1073741824),
|
roundedVal: int64(1073741824),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int64",
|
name: "round decimal to KB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(1288491),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to KB",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(9222246136947934),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to KB that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
roundedVal: int64(0),
|
roundedVal: int64(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -320,8 +360,18 @@ func Test_RoundUpToKiB(t *testing.T) {
|
|||||||
roundedVal: int64(1048576000),
|
roundedVal: int64(1048576000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int64",
|
name: "round decimal to KiB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(1258292),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to KiB",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(9006099743113216),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to KiB that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
roundedVal: int64(0),
|
roundedVal: int64(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -385,8 +435,18 @@ func Test_RoundUpToGiBInt32(t *testing.T) {
|
|||||||
roundedVal: int32(1000),
|
roundedVal: int32(1000),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "round overflowed quantity to int32",
|
name: "round decimal to GiB",
|
||||||
resource: resource.MustParse("73786976299133170k"),
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int32(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to GiB",
|
||||||
|
resource: resource.MustParse("2047Pi"),
|
||||||
|
roundedVal: int32(2146435072),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to GiB that would lead to an int32 overflow",
|
||||||
|
resource: resource.MustParse("2048Pi"),
|
||||||
roundedVal: int32(0),
|
roundedVal: int32(0),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
@ -411,3 +471,53 @@ func Test_RoundUpToGiBInt32(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_RoundUpToB(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
resource resource.Quantity
|
||||||
|
roundedVal int64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "round m to B",
|
||||||
|
resource: resource.MustParse("987m"),
|
||||||
|
roundedVal: int64(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round decimal to B",
|
||||||
|
resource: resource.MustParse("1.2Gi"),
|
||||||
|
roundedVal: int64(1288490189),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round big value to B",
|
||||||
|
resource: resource.MustParse("8191Pi"),
|
||||||
|
roundedVal: int64(9222246136947933184),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "round quantity to B that would lead to an int64 overflow",
|
||||||
|
resource: resource.MustParse("8192Pi"),
|
||||||
|
roundedVal: int64(0),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testcases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
val, err := RoundUpToB(test.resource)
|
||||||
|
if !test.expectError && err != nil {
|
||||||
|
t.Errorf("expected no error got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.expectError && err == nil {
|
||||||
|
t.Errorf("expected error but got nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != test.roundedVal {
|
||||||
|
t.Logf("actual rounded value: %d", val)
|
||||||
|
t.Logf("expected rounded value: %d", test.roundedVal)
|
||||||
|
t.Error("unexpected rounded value")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user