Skip to content

Commit 352f679

Browse files
authored
Merge pull request #2061 from sunnylovestiramisu/release-1.17
Fix Hyperdisk Resize That Requires Iops/Throughput Adjustment
2 parents 046be2a + ab221e9 commit 352f679

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

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)