Skip to content

Commit d9313fa

Browse files
GKE: Conditionally send the cpu_cfs_quota field based on presence in config (#15268) (#24569)
[upstream:606f5f2041c291dc7bd864cd7170eea55001461c] Signed-off-by: Modular Magician <[email protected]>
1 parent 99f50de commit d9313fa

File tree

5 files changed

+143
-5
lines changed

5 files changed

+143
-5
lines changed

.changelog/15268.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:breaking-change
2+
container: `node_config` blocks that had set `kubelet_config` without explicitly setting `cpu_cfs_quota` implicitly set `cfu_cfs_quota` to `false` when unset. From this version onwards, an unset `cpu_cfs_quota` will instead match the API default of true `true`. Resources that are recreated will receive the new value; old resources are unaffected, and may change values by explicitly setting the intended one.
3+
```
4+
5+
```release-note:bug
6+
container: Fixed the default for `node_config.kubelet_config.cpu_cfs_quota` on `google_container_cluster`, `google_container_node_pool`, `google_container_cluster.node_pool` to align with the API. Terraform will now send a `true` value when the field is unset on creation, and preserve any previously set value when unset. Explicitly set values will work as defined in configuration.
7+
```

google/services/container/node_config.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package container
1818

1919
import (
20+
"fmt"
2021
"log"
22+
"strconv"
2123
"strings"
2224
"time"
2325

26+
"github.com/hashicorp/go-cty/cty"
2427
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2528
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
2629

@@ -628,6 +631,7 @@ func schemaNodeConfig() *schema.Schema {
628631
},
629632
"cpu_cfs_quota": {
630633
Type: schema.TypeBool,
634+
Computed: true,
631635
Optional: true,
632636
Description: `Enable CPU CFS quota enforcement for containers that specify CPU limits.`,
633637
},
@@ -1181,7 +1185,7 @@ func expandNodeConfigDefaults(configured interface{}) *container.NodeConfigDefau
11811185
return nodeConfigDefaults
11821186
}
11831187

1184-
func expandNodeConfig(v interface{}) *container.NodeConfig {
1188+
func expandNodeConfig(d *schema.ResourceData, prefix string, v interface{}) *container.NodeConfig {
11851189
nodeConfigs := v.([]interface{})
11861190
nc := &container.NodeConfig{
11871191
// Defaults can't be set on a list/set in the schema, so set the default on create here.
@@ -1448,6 +1452,34 @@ func expandNodeConfig(v interface{}) *container.NodeConfig {
14481452

14491453
if v, ok := nodeConfig["kubelet_config"]; ok {
14501454
nc.KubeletConfig = expandKubeletConfig(v)
1455+
1456+
// start cpu_cfs_quota fix https://github.com/hashicorp/terraform-provider-google/issues/15767
1457+
// this makes the field conditional on appearance in configuration. This allows the API `true` default
1458+
// to override null, where currently we force-send null as false, which is wrong.
1459+
rawConfigNPRoot := d.GetRawConfig()
1460+
// if we have a prefix, we're in `node_pool.N.` in GKE Cluster. Traverse the RawConfig object to reach that
1461+
// root, at which point local references work going forwards.
1462+
if prefix != "" {
1463+
parts := strings.Split(prefix, ".") // "node_pool.N." -> ["node_pool" "N", ""]
1464+
npIndex, err := strconv.Atoi(parts[1])
1465+
if err != nil { // no error return from expander
1466+
panic(fmt.Errorf("unexpected format for node pool path prefix: %w. value: %v", err, prefix))
1467+
}
1468+
1469+
rawConfigNPRoot = rawConfigNPRoot.GetAttr("node_pool").Index(cty.NumberIntVal(int64(npIndex)))
1470+
}
1471+
1472+
if vNC := rawConfigNPRoot.GetAttr("node_config"); vNC.LengthInt() > 0 {
1473+
if vKC := vNC.Index(cty.NumberIntVal(0)).GetAttr("kubelet_config"); vKC.LengthInt() > 0 {
1474+
v := vKC.Index(cty.NumberIntVal(0)).GetAttr("cpu_cfs_quota")
1475+
if v == cty.NullVal(cty.Bool) {
1476+
nc.KubeletConfig.CpuCfsQuota = true
1477+
} else if v.False() { // force-send explicit false to API
1478+
nc.KubeletConfig.ForceSendFields = append(nc.KubeletConfig.ForceSendFields, "CpuCfsQuota")
1479+
}
1480+
}
1481+
}
1482+
// end cpu_cfs_quota fix
14511483
}
14521484

14531485
if v, ok := nodeConfig["linux_node_config"]; ok {
@@ -1586,7 +1618,6 @@ func expandKubeletConfig(v interface{}) *container.NodeKubeletConfig {
15861618
}
15871619
if cpuCfsQuota, ok := cfg["cpu_cfs_quota"]; ok {
15881620
kConfig.CpuCfsQuota = cpuCfsQuota.(bool)
1589-
kConfig.ForceSendFields = append(kConfig.ForceSendFields, "CpuCfsQuota")
15901621
}
15911622
if cpuCfsQuotaPeriod, ok := cfg["cpu_cfs_quota_period"]; ok {
15921623
kConfig.CpuCfsQuotaPeriod = cpuCfsQuotaPeriod.(string)

google/services/container/resource_container_cluster.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,15 +2675,15 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er
26752675
} else {
26762676
// Node Configs have default values that are set in the expand function,
26772677
// but can only be set if node pools are unspecified.
2678-
cluster.NodeConfig = expandNodeConfig([]interface{}{})
2678+
cluster.NodeConfig = expandNodeConfig(d, "", []interface{}{})
26792679
}
26802680

26812681
if v, ok := d.GetOk("node_pool_defaults"); ok {
26822682
cluster.NodePoolDefaults = expandNodePoolDefaults(v)
26832683
}
26842684

26852685
if v, ok := d.GetOk("node_config"); ok {
2686-
cluster.NodeConfig = expandNodeConfig(v)
2686+
cluster.NodeConfig = expandNodeConfig(d, "", v)
26872687
}
26882688

26892689
if v, ok := d.GetOk("authenticator_groups_config"); ok {

google/services/container/resource_container_cluster_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6386,6 +6386,42 @@ func TestAccContainerCluster_additional_pod_ranges_config_on_update(t *testing.T
63866386
})
63876387
}
63886388

6389+
func TestAccContainerCluster_withCpuCfsQuotaPool(t *testing.T) {
6390+
t.Parallel()
6391+
6392+
clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10))
6393+
npName := fmt.Sprintf("tf-test-cluster-nodepool-%s", acctest.RandString(t, 10))
6394+
networkName := acctest.BootstrapSharedTestNetwork(t, "gke-cluster")
6395+
subnetworkName := acctest.BootstrapSubnet(t, "gke-cluster", networkName)
6396+
6397+
acctest.VcrTest(t, resource.TestCase{
6398+
PreCheck: func() { acctest.AccTestPreCheck(t) },
6399+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
6400+
CheckDestroy: testAccCheckContainerClusterDestroyProducer(t),
6401+
Steps: []resource.TestStep{
6402+
{
6403+
Config: testAccContainerCluster_withCpuCfsQuotaPool(clusterName, npName, networkName, subnetworkName),
6404+
},
6405+
{
6406+
ResourceName: "google_container_cluster.with_kubelet_config",
6407+
ImportState: true,
6408+
ImportStateVerify: true,
6409+
ImportStateVerifyIgnore: []string{"deletion_protection"},
6410+
},
6411+
{
6412+
Config: testAccContainerCluster_withCpuCfsQuotaPool2(clusterName, npName, networkName, subnetworkName),
6413+
PlanOnly: true,
6414+
},
6415+
{
6416+
ResourceName: "google_container_cluster.with_kubelet_config",
6417+
ImportState: true,
6418+
ImportStateVerify: true,
6419+
ImportStateVerifyIgnore: []string{"deletion_protection"},
6420+
},
6421+
},
6422+
})
6423+
}
6424+
63896425
func testAccContainerCluster_masterAuthorizedNetworksDisabled(t *testing.T, resource_name string) resource.TestCheckFunc {
63906426
return func(s *terraform.State) error {
63916427
rs, ok := s.RootModule().Resources[resource_name]
@@ -13983,3 +14019,67 @@ resource "google_container_cluster" "with_kubelet_config" {
1398314019
}
1398414020
`, clusterName, networkName, subnetworkName, cpuManagerPolicy, memoryManagerPolicy, topologyManagerPolicy, topologyManagerScope)
1398514021
}
14022+
14023+
func testAccContainerCluster_withCpuCfsQuotaPool(clusterName, npName, networkName, subnetworkName string) string {
14024+
return fmt.Sprintf(`
14025+
resource "google_container_cluster" "with_kubelet_config" {
14026+
name = %q
14027+
location = "us-central1-a"
14028+
network = %q
14029+
subnetwork = %q
14030+
deletion_protection = false
14031+
14032+
node_pool {
14033+
name = "%s-1"
14034+
initial_node_count = 1
14035+
node_config {
14036+
kubelet_config {
14037+
# cpu_cfs_quota = true
14038+
}
14039+
}
14040+
}
14041+
14042+
node_pool {
14043+
name = "%s-2"
14044+
initial_node_count = 1
14045+
node_config {
14046+
kubelet_config {
14047+
cpu_cfs_quota = false
14048+
}
14049+
}
14050+
}
14051+
}
14052+
`, clusterName, networkName, subnetworkName, npName, npName)
14053+
}
14054+
14055+
func testAccContainerCluster_withCpuCfsQuotaPool2(clusterName, npName, networkName, subnetworkName string) string {
14056+
return fmt.Sprintf(`
14057+
resource "google_container_cluster" "with_kubelet_config" {
14058+
name = %q
14059+
location = "us-central1-a"
14060+
network = %q
14061+
subnetwork = %q
14062+
deletion_protection = false
14063+
14064+
node_pool {
14065+
name = "%s-1"
14066+
initial_node_count = 1
14067+
node_config {
14068+
kubelet_config {
14069+
cpu_cfs_quota = true
14070+
}
14071+
}
14072+
}
14073+
14074+
node_pool {
14075+
name = "%s-2"
14076+
initial_node_count = 1
14077+
node_config {
14078+
kubelet_config {
14079+
cpu_cfs_quota = false
14080+
}
14081+
}
14082+
}
14083+
}
14084+
`, clusterName, networkName, subnetworkName, npName, npName)
14085+
}

google/services/container/resource_container_node_pool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,7 @@ func expandNodePool(d *schema.ResourceData, prefix string) (*container.NodePool,
968968
np := &container.NodePool{
969969
Name: name,
970970
InitialNodeCount: int64(nodeCount),
971-
Config: expandNodeConfig(d.Get(prefix + "node_config")),
971+
Config: expandNodeConfig(d, prefix, d.Get(prefix+"node_config")),
972972
Locations: locations,
973973
Version: d.Get(prefix + "version").(string),
974974
NetworkConfig: expandNodeNetworkConfig(d.Get(prefix + "network_config")),

0 commit comments

Comments
 (0)