Skip to content

Commit 2858b94

Browse files
authored
Merge pull request kubernetes-sigs#2054 from sunnylovestiramisu/resizeFix
Fix Hyperdisk Resize That Requires Iops/Throughput Adjustment
2 parents 3fcbdda + 3893576 commit 2858b94

File tree

3 files changed

+359
-4
lines changed

3 files changed

+359
-4
lines changed

pkg/common/utils.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
3131
"github.com/googleapis/gax-go/v2/apierror"
3232
"golang.org/x/time/rate"
33+
computev1 "google.golang.org/api/compute/v1"
3334
"google.golang.org/api/googleapi"
3435
"google.golang.org/grpc/codes"
3536
"google.golang.org/grpc/status"
@@ -80,6 +81,12 @@ const (
8081
// projects/{project}/zones/{zone}
8182
zoneURIPattern = "projects/[^/]+/zones/([^/]+)$"
8283
alphanums = "bcdfghjklmnpqrstvwxz2456789"
84+
85+
HyperdiskBalancedIopsPerGB = 500
86+
HyperdiskBalancedMinIops = 3000
87+
HyperdiskExtremeIopsPerGB = 2
88+
HyperdiskThroughputThroughputPerGB = 10
89+
BytesInGB = 1024
8390
)
8491

8592
var (
@@ -768,3 +775,75 @@ func MapNumber(num int64) int64 {
768775
func DiskTypeLabelKey(diskType string) string {
769776
return fmt.Sprintf("%s/%s", DiskTypeKeyPrefix, diskType)
770777
}
778+
779+
// IsUpdateIopsThroughputValuesAllowed checks if a disk type is hyperdisk,
780+
// which implies that IOPS and throughput values can be updated.
781+
func IsUpdateIopsThroughputValuesAllowed(disk *computev1.Disk) bool {
782+
// Sample formats:
783+
// https://www.googleapis.com/compute/v1/{gce.projectID}/zones/{disk.Zone}/diskTypes/{disk.Type}"
784+
// https://www.googleapis.com/compute/v1/{gce.projectID}/regions/{disk.Region}/diskTypes/{disk.Type}"
785+
return strings.Contains(disk.Type, "hyperdisk")
786+
}
787+
788+
// GetMinIopsThroughput calculates and returns the minimum required IOPS and throughput
789+
// based on the existing disk configuration and the requested new GiB.
790+
// The `needed` return value indicates whether either IOPS or throughput need to be updated.
791+
// https://cloud.google.com/compute/docs/disks/hyperdisks#limits-disk
792+
func GetMinIopsThroughput(disk *computev1.Disk, requestGb int64) (needed bool, minIops int64, minThroughput int64) {
793+
switch {
794+
case strings.Contains(disk.Type, "hyperdisk-balanced"):
795+
// This includes types "hyperdisk-balanced" and "hyperdisk-balanced-high-availability"
796+
return minIopsForBalanced(disk, requestGb)
797+
case strings.Contains(disk.Type, "hyperdisk-extreme"):
798+
return minIopsForExtreme(disk, requestGb)
799+
case strings.Contains(disk.Type, "hyperdisk-ml"):
800+
return minThroughputForML(disk, requestGb)
801+
case strings.Contains(disk.Type, "hyperdisk-throughput"):
802+
return minThroughputForThroughput(disk, requestGb)
803+
default:
804+
return false, 0, 0
805+
}
806+
}
807+
808+
// minIopsForBalanced calculates and returns the minimum required IOPS and throughput
809+
// for hyperdisk-balanced and hyperdisk-balanced-high-availability disks
810+
func minIopsForBalanced(disk *computev1.Disk, requestGb int64) (needed bool, minIops int64, minThroughput int64) {
811+
minRequiredIops := requestGb * HyperdiskBalancedIopsPerGB
812+
if minRequiredIops > HyperdiskBalancedMinIops {
813+
minRequiredIops = HyperdiskBalancedMinIops
814+
}
815+
if disk.ProvisionedIops < minRequiredIops {
816+
return true, minRequiredIops, 0
817+
}
818+
return false, 0, 0
819+
}
820+
821+
// minIopsForExtreme calculates and returns the minimum required IOPS and throughput
822+
// for hyperdisk-extreme disks
823+
func minIopsForExtreme(disk *computev1.Disk, requestGb int64) (needed bool, minIops int64, minThroughput int64) {
824+
minRequiredIops := requestGb * HyperdiskExtremeIopsPerGB
825+
if disk.ProvisionedIops < minRequiredIops {
826+
return true, minRequiredIops, 0
827+
}
828+
return false, 0, 0
829+
}
830+
831+
// minThroughputForML calculates and returns the minimum required IOPS and throughput
832+
// for hyperdisk-ml disks
833+
func minThroughputForML(disk *computev1.Disk, requestGb int64) (needed bool, minIops int64, minThroughput int64) {
834+
minRequiredThroughput := int64(float64(requestGb) * 0.12)
835+
if disk.ProvisionedThroughput < minRequiredThroughput {
836+
return true, 0, minRequiredThroughput
837+
}
838+
return false, 0, 0
839+
}
840+
841+
// minThroughputForThroughput calculates and returns the minimum required IOPS and throughput
842+
// for hyperdisk-throughput disks
843+
func minThroughputForThroughput(disk *computev1.Disk, requestGb int64) (needed bool, minIops int64, minThroughput int64) {
844+
minRequiredThroughput := requestGb * HyperdiskThroughputThroughputPerGB / BytesInGB
845+
if disk.ProvisionedThroughput < minRequiredThroughput {
846+
return true, 0, minRequiredThroughput
847+
}
848+
return false, 0, 0
849+
}

pkg/common/utils_test.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta"
2929
"github.com/google/go-cmp/cmp"
3030
"github.com/googleapis/gax-go/v2/apierror"
31+
computev1 "google.golang.org/api/compute/v1"
3132
"google.golang.org/api/googleapi"
3233
"google.golang.org/grpc/codes"
3334
"google.golang.org/grpc/status"
@@ -36,6 +37,7 @@ import (
3637
const (
3738
volIDZoneFmt = "projects/%s/zones/%s/disks/%s"
3839
volIDRegionFmt = "projects/%s/regions/%s/disks/%s"
40+
testDiskName = "test-disk"
3941
)
4042

4143
func TestBytesToGbRoundDown(t *testing.T) {
@@ -1954,3 +1956,196 @@ func TestNewCombinedError(t *testing.T) {
19541956
})
19551957
}
19561958
}
1959+
func TestIsUpdateIopsThroughputValuesAllowed(t *testing.T) {
1960+
testcases := []struct {
1961+
name string
1962+
diskType string
1963+
expectResult bool
1964+
}{
1965+
{
1966+
name: "Hyperdisk returns true",
1967+
diskType: "hyperdisk-balanced",
1968+
expectResult: true,
1969+
},
1970+
{
1971+
name: "PD disk returns true",
1972+
diskType: "pd-ssd",
1973+
expectResult: false,
1974+
},
1975+
{
1976+
name: "Unknown disk type",
1977+
diskType: "not-a-disk-type-we-know",
1978+
expectResult: false,
1979+
},
1980+
}
1981+
for _, tc := range testcases {
1982+
t.Run(tc.name, func(t *testing.T) {
1983+
disk := &computev1.Disk{
1984+
Name: "test-disk",
1985+
Type: tc.diskType,
1986+
}
1987+
gotResult := IsUpdateIopsThroughputValuesAllowed(disk)
1988+
if gotResult != tc.expectResult {
1989+
t.Errorf("IsUpdateIopsThroughputValuesAllowed: got %v, want %v", gotResult, tc.expectResult)
1990+
}
1991+
})
1992+
}
1993+
}
1994+
1995+
func TestGetMinIopsThroughput(t *testing.T) {
1996+
testcases := []struct {
1997+
name string
1998+
existingDisk *computev1.Disk
1999+
reqGb int64
2000+
expectResult bool
2001+
expectMinIops int64
2002+
expectMinThroughput int64
2003+
}{
2004+
{
2005+
name: "Hyperdisk Balanced 4 GiB to 5GiB",
2006+
existingDisk: &computev1.Disk{
2007+
Name: testDiskName,
2008+
Type: "hyperdisk-balanced",
2009+
ProvisionedIops: 2000,
2010+
ProvisionedThroughput: 140,
2011+
SizeGb: 4,
2012+
},
2013+
reqGb: 5,
2014+
expectResult: true,
2015+
expectMinIops: 2500,
2016+
expectMinThroughput: 0, // 0 indicates no change to throughput
2017+
},
2018+
{
2019+
name: "Hyperdisk Balanced 5 GiB to 6GiB",
2020+
existingDisk: &computev1.Disk{
2021+
Name: testDiskName,
2022+
Type: "hyperdisk-balanced",
2023+
ProvisionedIops: 2500,
2024+
ProvisionedThroughput: 145,
2025+
SizeGb: 5,
2026+
},
2027+
reqGb: 6,
2028+
expectResult: true,
2029+
expectMinIops: 3000,
2030+
expectMinThroughput: 0, // 0 indicates no change to throughput
2031+
},
2032+
{
2033+
name: "Hyperdisk Balanced 6 GiB to 10GiB - no adjustment",
2034+
existingDisk: &computev1.Disk{
2035+
Name: testDiskName,
2036+
Type: "hyperdisk-balanced",
2037+
ProvisionedIops: 3000,
2038+
ProvisionedThroughput: 145,
2039+
SizeGb: 6,
2040+
},
2041+
reqGb: 10,
2042+
expectResult: false,
2043+
expectMinIops: 0, // 0 indicates no change to iops
2044+
expectMinThroughput: 0, // 0 indicates no change to throughput
2045+
},
2046+
{
2047+
name: "Hyperdisk Extreme with min IOPS value as 2 will adjust IOPs",
2048+
existingDisk: &computev1.Disk{
2049+
Name: testDiskName,
2050+
Type: "hyperdisk-extreme",
2051+
ProvisionedIops: 128,
2052+
SizeGb: 64,
2053+
},
2054+
reqGb: 65,
2055+
expectResult: true,
2056+
expectMinIops: 130,
2057+
expectMinThroughput: 0, // 0 indicates no change to throughput
2058+
},
2059+
{
2060+
name: "Hyperdisk Extreme 64GiB to 70 GiB - no adjustment",
2061+
existingDisk: &computev1.Disk{
2062+
Name: testDiskName,
2063+
Type: "hyperdisk-extreme",
2064+
ProvisionedIops: 3000,
2065+
SizeGb: 64,
2066+
},
2067+
reqGb: 70,
2068+
expectResult: false,
2069+
expectMinIops: 0, // 0 indicates no change to iops
2070+
expectMinThroughput: 0, // 0 indicates no change to throughput
2071+
},
2072+
{
2073+
name: "Hyperdisk ML with min throughput per GB will adjust throughput",
2074+
existingDisk: &computev1.Disk{
2075+
Name: testDiskName,
2076+
Type: "hyperdisk-ml",
2077+
ProvisionedThroughput: 400,
2078+
SizeGb: 3334,
2079+
},
2080+
reqGb: 3400,
2081+
expectResult: true,
2082+
expectMinThroughput: 408,
2083+
},
2084+
{
2085+
name: "Hyperdisk ML 64GiB to 100 GiB - no adjustment",
2086+
existingDisk: &computev1.Disk{
2087+
Name: testDiskName,
2088+
Type: "hyperdisk-ml",
2089+
ProvisionedThroughput: 6400,
2090+
SizeGb: 64,
2091+
},
2092+
reqGb: 100,
2093+
expectResult: false,
2094+
expectMinIops: 0, // 0 indicates no change to iops
2095+
expectMinThroughput: 0, // 0 indicates no change to throughput
2096+
},
2097+
{
2098+
name: "Hyperdisk throughput with min throughput per GB will adjust throughput",
2099+
existingDisk: &computev1.Disk{
2100+
Name: testDiskName,
2101+
Type: "hyperdisk-throughput",
2102+
ProvisionedThroughput: 20,
2103+
SizeGb: 2048,
2104+
},
2105+
reqGb: 3072,
2106+
expectResult: true,
2107+
expectMinIops: 0,
2108+
expectMinThroughput: 30,
2109+
},
2110+
{
2111+
name: "Hyperdisk throughput 2TiB to 4TiB - no adjustment",
2112+
existingDisk: &computev1.Disk{
2113+
Name: testDiskName,
2114+
Type: "hyperdisk-throughput",
2115+
ProvisionedThroughput: 567,
2116+
SizeGb: 2048,
2117+
},
2118+
reqGb: 4096,
2119+
expectResult: false,
2120+
expectMinIops: 0, // 0 indicates no change to iops
2121+
expectMinThroughput: 0, // 0 indicates no change to throughput
2122+
},
2123+
{
2124+
name: "Unknown disk type, no need to update",
2125+
existingDisk: &computev1.Disk{
2126+
Name: testDiskName,
2127+
Type: "unknown-type",
2128+
},
2129+
reqGb: 5,
2130+
expectResult: false,
2131+
expectMinIops: 0,
2132+
expectMinThroughput: 0, // 0 indicates no change to throughput
2133+
},
2134+
}
2135+
for _, tc := range testcases {
2136+
t.Run(tc.name, func(t *testing.T) {
2137+
gotNeeded, gotMinIops, gotMinThroughput := GetMinIopsThroughput(tc.existingDisk, tc.reqGb)
2138+
if gotNeeded != tc.expectResult {
2139+
t.Errorf("GetMinIopsThroughput: got %v, want %v", gotNeeded, tc.expectResult)
2140+
}
2141+
2142+
if gotMinIops != tc.expectMinIops {
2143+
t.Errorf("GetMinIopsThroughput Iops: got %v, want %v", gotMinIops, tc.expectMinIops)
2144+
}
2145+
2146+
if gotMinThroughput != tc.expectMinThroughput {
2147+
t.Errorf("GetMinIopsThroughput Throughput: got %v, want %v", gotMinThroughput, tc.expectMinThroughput)
2148+
}
2149+
})
2150+
}
2151+
}

0 commit comments

Comments
 (0)