fix rounding of volume storage requests

This commit is contained in:
Maxime Lagresle 2021-03-10 19:44:39 +01:00
parent 35061acc28
commit ce7a7bbf9b
No known key found for this signature in database
GPG Key ID: B99224DCB4BABD82
3 changed files with 172 additions and 89 deletions

View File

@ -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 {

View File

@ -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
} }

View File

@ -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")
}
})
}
}