Merge pull request #100100 from maxlaverse/fix_pv_provisioning

fix rounding of volume storage requests
This commit is contained in:
Kubernetes Prow Robot
2021-04-08 17:12:04 -07:00
committed by GitHub
3 changed files with 187 additions and 91 deletions

View File

@@ -214,9 +214,9 @@ func (util *portworxVolumeUtil) ResizeVolume(spec *volume.Spec, newSize resource
}
vol := vols[0]
tBytes, ok := newSize.AsInt64()
if !ok {
return fmt.Errorf("quantity %v is too great, overflows int64", newSize)
tBytes, err := volumehelpers.RoundUpToB(newSize)
if err != nil {
return err
}
newSizeInBytes := uint64(tBytes)
if vol.Spec.Size >= newSizeInBytes {

View File

@@ -18,10 +18,22 @@ package helpers
import (
"fmt"
"math"
"math/bits"
"k8s.io/apimachinery/pkg/api/resource"
)
/*
The Cloud Provider's volume plugins provision disks for corresponding
PersistentVolumeClaims. Cloud Providers use different allocation unit for their
disk sizes. AWS allows you to specify the size as an integer amount of GiB,
while Portworx expects bytes for example. On AWS, if you want a volume of
1500MiB, the actual call to the AWS API should therefore be for a 2GiB disk.
This file contains functions that help rounding a storage request based on a
Cloud Provider's allocation unit.
*/
const (
// GB - GigaByte size
GB = 1000 * 1000 * 1000
@@ -41,139 +53,113 @@ const (
// RoundUpToGiB rounds up given quantity upto chunks of GiB
func RoundUpToGiB(size resource.Quantity) (int64, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSize(requestBytes, GiB), nil
return roundUpSizeInt64(size, GiB)
}
// RoundUpToMB rounds up given quantity to chunks of MB
func RoundUpToMB(size resource.Quantity) (int64, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSize(requestBytes, MB), nil
return roundUpSizeInt64(size, MB)
}
// RoundUpToMiB rounds up given quantity upto chunks of MiB
func RoundUpToMiB(size resource.Quantity) (int64, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSize(requestBytes, MiB), nil
return roundUpSizeInt64(size, MiB)
}
// RoundUpToKB rounds up given quantity to chunks of KB
func RoundUpToKB(size resource.Quantity) (int64, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSize(requestBytes, KB), nil
return roundUpSizeInt64(size, KB)
}
// 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) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
return roundUpSizeInt64(size, KiB)
}
return roundUpSize(requestBytes, KiB), nil
// RoundUpToB rounds up given quantity to chunks of bytes
func RoundUpToB(size resource.Quantity) (int64, error) {
return roundUpSizeInt64(size, 1)
}
// RoundUpToGiBInt rounds up given quantity upto chunks of GiB. It returns an
// int instead of an int64 and an error if there's overflow
func RoundUpToGiBInt(size resource.Quantity) (int, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSizeInt(requestBytes, GiB)
return roundUpSizeInt(size, GiB)
}
// RoundUpToMBInt rounds up given quantity to chunks of MB. It returns an
// int instead of an int64 and an error if there's overflow
func RoundUpToMBInt(size resource.Quantity) (int, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSizeInt(requestBytes, MB)
return roundUpSizeInt(size, MB)
}
// RoundUpToMiBInt rounds up given quantity upto chunks of MiB. It returns an
// int instead of an int64 and an error if there's overflow
func RoundUpToMiBInt(size resource.Quantity) (int, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSizeInt(requestBytes, MiB)
return roundUpSizeInt(size, MiB)
}
// RoundUpToKBInt rounds up given quantity to chunks of KB. It returns an
// int instead of an int64 and an error if there's overflow
func RoundUpToKBInt(size resource.Quantity) (int, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSizeInt(requestBytes, KB)
return roundUpSizeInt(size, KB)
}
// RoundUpToKiBInt rounds up given quantity upto chunks of KiB. It returns an
// int instead of an int64 and an error if there's overflow
func RoundUpToKiBInt(size resource.Quantity) (int, error) {
requestBytes := size.Value()
return roundUpSizeInt(requestBytes, KiB)
return roundUpSizeInt(size, KiB)
}
// 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
func RoundUpToGiBInt32(size resource.Quantity) (int32, error) {
requestBytes, ok := size.AsInt64()
if !ok {
return 0, fmt.Errorf("quantity %s is too great, overflows int64", size.String())
}
return roundUpSizeInt32(requestBytes, GiB)
return roundUpSizeInt32(size, GiB)
}
// 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
// there's overflow
func roundUpSizeInt(volumeSizeBytes int64, allocationUnitBytes int64) (int, error) {
roundedUp := roundUpSize(volumeSizeBytes, allocationUnitBytes)
roundedUpInt := int(roundedUp)
if int64(roundedUpInt) != roundedUp {
return 0, fmt.Errorf("capacity %v is too great, casting results in integer overflow", roundedUp)
// a volume of a given size. It returns an int and an error if there's overflow
func roundUpSizeInt(size resource.Quantity, allocationUnitBytes int64) (int, error) {
if bits.UintSize == 32 {
res, err := roundUpSizeInt32(size, allocationUnitBytes)
return int(res), err
}
return roundedUpInt, nil
res, err := roundUpSizeInt64(size, allocationUnitBytes)
return int(res), err
}
// 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
// there's overflow
func roundUpSizeInt32(volumeSizeBytes int64, allocationUnitBytes int64) (int32, error) {
roundedUp := roundUpSize(volumeSizeBytes, allocationUnitBytes)
roundedUpInt32 := int32(roundedUp)
if int64(roundedUpInt32) != roundedUp {
return 0, fmt.Errorf("quantity %v is too great, overflows int32", roundedUp)
// a volume of a given size. It returns an int32 and an error if there's overflow
func roundUpSizeInt32(size resource.Quantity, allocationUnitBytes int64) (int32, error) {
roundedUpInt32, err := roundUpSizeInt64(size, allocationUnitBytes)
if err != nil {
return 0, err
}
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
// a volume of given size. E.g. when user wants 1500MiB volume, while AWS EBS
// allocates volumes in gibibyte-sized chunks,
// RoundUpSize(1500 * 1024*1024, 1024*1024*1024) returns '2'
// (2 GiB is the smallest allocatable volume that can hold 1500MiB)
func roundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 {
// roundUpSizeInt64 calculates how many allocation units are needed to accommodate
// a volume of a given size. It returns an int64 and an error if there's overflow
func roundUpSizeInt64(size resource.Quantity, allocationUnitBytes int64) (int64, error) {
// Use CmpInt64() to find out if the value of "size" would overflow an
// int64 and therefore have Value() return a wrong result. Then, retrieve
// the value as int64 and perform the rounding.
// It's not convenient to use AsScale() and related functions as they don't
// support BinarySI format, nor can we use AsInt64() directly since it's
// only implemented for int64 scaled numbers (int64Amount).
// CmpInt64() actually returns 0 when comparing an amount bigger than 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
if volumeSizeBytes%allocationUnitBytes > 0 {
roundedUp++
}
return roundedUp
return roundedUp, nil
}

View File

@@ -60,8 +60,18 @@ func Test_RoundUpToGiB(t *testing.T) {
roundedVal: int64(1000),
},
{
name: "round overflowed quantity to int64",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to GiB",
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),
expectError: true,
},
@@ -125,8 +135,18 @@ func Test_RoundUpToMB(t *testing.T) {
roundedVal: int64(1073742),
},
{
name: "round overflowed quantity to int64",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to MB",
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),
expectError: true,
},
@@ -190,8 +210,18 @@ func Test_RoundUpToMiB(t *testing.T) {
roundedVal: int64(1024000),
},
{
name: "round overflowed quantity to int64",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to MiB",
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),
expectError: true,
},
@@ -255,8 +285,18 @@ func Test_RoundUpToKB(t *testing.T) {
roundedVal: int64(1073741824),
},
{
name: "round overflowed quantity to int64",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to KB",
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),
expectError: true,
},
@@ -320,8 +360,18 @@ func Test_RoundUpToKiB(t *testing.T) {
roundedVal: int64(1048576000),
},
{
name: "round overflowed quantity to int64",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to KiB",
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),
expectError: true,
},
@@ -385,8 +435,18 @@ func Test_RoundUpToGiBInt32(t *testing.T) {
roundedVal: int32(1000),
},
{
name: "round overflowed quantity to int32",
resource: resource.MustParse("73786976299133170k"),
name: "round decimal to GiB",
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),
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")
}
})
}
}