Repository: kubernetes-sigs/gcp-compute-persistent-disk-csi-driver
Pull request #2127 (2025-07-09 01:25:07)

fix: update Hyperdisk attach limits to match GCP documentation for Ge…
**What type of PR is this?**

> /kind bug

**What this PR does / why we need it**:
This PR updates the Hyperdisk balanced attach limits to align with the latest GCP documentation for Gen4 machine families (`C4`, `C4D`, `N4`, and `C4A`).
Previously, a single generic map was used, which did not reflect the correct per-family attach limits for newer machine types.

Key changes:

* Adds separate Hyperdisk attach limit maps for `C4`, `C4D`, `N4`, and `C4A` machines.
* Updates the attach limit calculation logic to use the appropriate map based on the machine type prefix.
* Updates unit tests to cover new machine type mappings and verify correct limits.

This fixes inaccurate attach limits for Gen4 machines, ensuring compliance with the [[official GCP guidance](https://cloud.google.com/compute/docs/general-purpose-machines)](https://cloud.google.com/compute/docs/general-purpose-machines).

**Which issue(s) this PR fixes**:
Fixes #<!-- TODO: link related issue if you have one -->

**Special notes for your reviewer**:

* Please verify the machine type prefixes and limit values match the official GCP tables.


```release-note
fix: Updated Hyperdisk balanced attach limits for C4, C4D, N4, and C4A machine families to match current GCP documentation.
```

k8s-ci-robot: Welcome @arsiesys! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver'>kubernetes-sigs/gcp-compute-persistent-disk-csi-driver</a> 🎉. Please refer to our [pull request process documentation](https://www.kubernetes.dev/docs/guide/pull-requests/) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes-sigs/gcp-compute-persistent-disk-csi-driver has [its own contribution guidelines](https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://www.kubernetes.dev/docs/guide/pull-requests/#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://www.kubernetes.dev/docs/contributor-cheatsheet/). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @arsiesys. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fgcp-compute-persistent-disk-csi-driver).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

sunnylovestiramisu: /ok-to-test
k8s-ci-robot: @arsiesys: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022 | 6df040f4e4dfcbc4525949eed0b733d0b40b2e7f | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_gcp-compute-persistent-disk-csi-driver/2127/pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022/1942982346470854656) | false | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=2127). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Aarsiesys). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @arsiesys: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022 | 6df040f4e4dfcbc4525949eed0b733d0b40b2e7f | [link](https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/kubernetes-sigs_gcp-compute-persistent-disk-csi-driver/2127/pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022/1943037367321366528) | false | `/test pull-gcp-compute-persistent-disk-csi-driver-e2e-windows-2022`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-compute-persistent-disk-csi-driver&pr=2127). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Aarsiesys). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
sunnylovestiramisu: /assgn @mattcary 

Patch:
diff --git a/pkg/common/constants.go b/pkg/common/constants.go
index 46a6d57b..ec218c13 100644
--- a/pkg/common/constants.go
+++ b/pkg/common/constants.go
@@ -65,15 +65,42 @@ const (
 	AttachLimitOverrideLabel   = "gke-volume-attach-limit-override"
 )
 
-// doc https://cloud.google.com/compute/docs/disks/hyperdisks#max-total-disks-per-vm
-var Gen4MachineHyperdiskAttachLimitMap = []struct {
+// doc https://cloud.google.com/compute/docs/general-purpose-machines
+// MachineHyperdiskLimit represents the mapping between max vCPUs and hyperdisk (balanced) attach limit
+type MachineHyperdiskLimit struct {
 	max   int64
 	value int64
-}{
+}
+
+// C4 Machine Types - Hyperdisk Balanced Limits
+var C4MachineHyperdiskAttachLimitMap = []MachineHyperdiskLimit{
+	{max: 2, value: 7},
 	{max: 4, value: 15},
-	{max: 8, value: 23},
-	{max: 16, value: 31},
-	{max: 32, value: 49},
-	{max: 64, value: 63},
-	{max: 1024, value: 127},
+	{max: 24, value: 31},
+	{max: 48, value: 63},
+	{max: 96, value: 127},
+}
+
+// C4D Machine Types - Hyperdisk Balanced Limits
+var C4DMachineHyperdiskAttachLimitMap = []MachineHyperdiskLimit{
+	{max: 2, value: 3},
+	{max: 4, value: 7},
+	{max: 8, value: 15},
+	{max: 96, value: 31},
+	{max: 192, value: 63},
+	{max: 384, value: 127},
+}
+
+// N4 Machine Types - Hyperdisk Balanced Limits
+var N4MachineHyperdiskAttachLimitMap = []MachineHyperdiskLimit{
+	{max: 8, value: 15},
+	{max: 80, value: 31},
+}
+
+// C4A Machine Types - Hyperdisk Balanced Limits
+var C4AMachineHyperdiskAttachLimitMap = []MachineHyperdiskLimit{
+	{max: 2, value: 7},
+	{max: 8, value: 15},
+	{max: 48, value: 31},
+	{max: 72, value: 63},
 }
diff --git a/pkg/common/utils.go b/pkg/common/utils.go
index b15181b3..23ca1839 100644
--- a/pkg/common/utils.go
+++ b/pkg/common/utils.go
@@ -762,14 +762,39 @@ func ShortString(s string) string {
 	return string(short)
 }
 
-// MapNumber is a function to map input cpu number to the Hyperdisk attach limit
-func MapNumber(num int64) int64 {
-	for _, r := range Gen4MachineHyperdiskAttachLimitMap {
-		if num <= r.max {
-			return r.value
+// GetHyperdiskAttachLimit returns the hyperdisk attach limit based on machine type prefix and vCPUs
+func GetHyperdiskAttachLimit(machineTypePrefix string, vCPUs int64) int64 {
+	var limitMap []MachineHyperdiskLimit
+
+	switch machineTypePrefix {
+	case "c4":
+		limitMap = C4MachineHyperdiskAttachLimitMap
+	case "c4d":
+		limitMap = C4DMachineHyperdiskAttachLimitMap
+	case "n4":
+		limitMap = N4MachineHyperdiskAttachLimitMap
+	case "c4a":
+		limitMap = C4AMachineHyperdiskAttachLimitMap
+	default:
+		// Fallback to the most conservative Gen4 map for unknown types
+		return MapNumber(vCPUs, C4DMachineHyperdiskAttachLimitMap)
+	}
+
+	return MapNumber(vCPUs, limitMap)
+}
+
+// mapNumber maps the vCPUs to the appropriate hyperdisk limit
+func MapNumber(vCPUs int64, limitMap []MachineHyperdiskLimit) int64 {
+	for _, limit := range limitMap {
+		if vCPUs <= limit.max {
+			return limit.value
 		}
 	}
-	return 0
+	// Return the last value if vCPUs exceeds all max values
+	if len(limitMap) > 0 {
+		return limitMap[len(limitMap)-1].value
+	}
+	return 15
 }
 
 func DiskTypeLabelKey(diskType string) string {
diff --git a/pkg/gce-pd-csi-driver/node.go b/pkg/gce-pd-csi-driver/node.go
index cb430355..c31be82c 100644
--- a/pkg/gce-pd-csi-driver/node.go
+++ b/pkg/gce-pd-csi-driver/node.go
@@ -858,17 +858,19 @@ func (ns *GCENodeServer) GetVolumeLimits(ctx context.Context) (int64, error) {
 				if err != nil {
 					return volumeLimitBig, fmt.Errorf("invalid cpuString %s for machine type: %v", cpuString, machineType)
 				}
-				return common.MapNumber(cpus), nil
+				// Extract the machine type prefix (e.g., "c4", "c4a", "n4")
+				prefix := strings.TrimSuffix(gen4Prefix, "-")
+				return common.GetHyperdiskAttachLimit(prefix, cpus), nil
 			} else {
 				return volumeLimitBig, fmt.Errorf("unconventional machine type: %v", machineType)
 			}
 		}
-		if strings.HasPrefix(machineType, "x4-") {
-			return x4HyperdiskLimit, nil
-		}
-		if strings.HasPrefix(machineType, "a4-") {
-			return a4HyperdiskLimit, nil
-		}
+	}
+	if strings.HasPrefix(machineType, "x4-") {
+		return x4HyperdiskLimit, nil
+	}
+	if strings.HasPrefix(machineType, "a4-") {
+		return a4HyperdiskLimit, nil
 	}
 
 	return volumeLimitBig, nil


Test patch:
diff --git a/pkg/gce-pd-csi-driver/node_test.go b/pkg/gce-pd-csi-driver/node_test.go
index 4d2883ee..4f64b610 100644
--- a/pkg/gce-pd-csi-driver/node_test.go
+++ b/pkg/gce-pd-csi-driver/node_test.go
@@ -278,6 +278,11 @@ func TestNodeGetVolumeLimits(t *testing.T) {
 			machineType:    "c4-standard-48",
 			expVolumeLimit: 63,
 		},
+		{
+			name:           "c4-standard-2",
+			machineType:    "c4-standard-2",
+			expVolumeLimit: 7,
+		},
 		{
 			name:           "c4a-standard-4",
 			machineType:    "c4a-standard-4",
@@ -302,7 +307,7 @@ func TestNodeGetVolumeLimits(t *testing.T) {
 		{
 			name:           "n4-custom-8-12345-ext",
 			machineType:    "n4-custom-8-12345-ext",
-			expVolumeLimit: 23,
+			expVolumeLimit: 15,
 		},
 		{
 			name:           "n4-custom-16-12345",
@@ -338,12 +343,12 @@ func TestNodeGetVolumeLimits(t *testing.T) {
 		{
 			name:           "c4a-standard-32-lssd",
 			machineType:    "c4a-standard-32-lssd",
-			expVolumeLimit: 49,
+			expVolumeLimit: 31,
 		},
 		{
 			name:           "c4d-standard-32",
 			machineType:    "c4d-standard-32",
-			expVolumeLimit: 49,
+			expVolumeLimit: 31,
 		},
 	}