Skip to content

Commit 606f5f2

Browse files
authored
GKE: Conditionally send the cpu_cfs_quota field based on presence in config (#15268)
1 parent 2508778 commit 606f5f2

File tree

4 files changed

+136
-5
lines changed

4 files changed

+136
-5
lines changed

mmv1/third_party/terraform/services/container/node_config.go.tmpl

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package container
22

33
import (
4+
"fmt"
45
"log"
6+
"strconv"
57
"strings"
68
"time"
79

10+
"github.com/hashicorp/go-cty/cty"
811
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
912
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1013

@@ -659,6 +662,7 @@ func schemaNodeConfig() *schema.Schema {
659662
},
660663
"cpu_cfs_quota": {
661664
Type: schema.TypeBool,
665+
Computed: true,
662666
Optional: true,
663667
Description: `Enable CPU CFS quota enforcement for containers that specify CPU limits.`,
664668
},
@@ -1213,7 +1217,7 @@ func expandNodeConfigDefaults(configured interface{}) *container.NodeConfigDefau
12131217
return nodeConfigDefaults
12141218
}
12151219

1216-
func expandNodeConfig(v interface{}) *container.NodeConfig {
1220+
func expandNodeConfig(d *schema.ResourceData, prefix string, v interface{}) *container.NodeConfig {
12171221
nodeConfigs := v.([]interface{})
12181222
nc := &container.NodeConfig{
12191223
// Defaults can't be set on a list/set in the schema, so set the default on create here.
@@ -1496,6 +1500,34 @@ func expandNodeConfig(v interface{}) *container.NodeConfig {
14961500

14971501
if v, ok := nodeConfig["kubelet_config"]; ok {
14981502
nc.KubeletConfig = expandKubeletConfig(v)
1503+
1504+
// start cpu_cfs_quota fix https://github.com/hashicorp/terraform-provider-google/issues/15767
1505+
// this makes the field conditional on appearance in configuration. This allows the API `true` default
1506+
// to override null, where currently we force-send null as false, which is wrong.
1507+
rawConfigNPRoot := d.GetRawConfig()
1508+
// if we have a prefix, we're in `node_pool.N.` in GKE Cluster. Traverse the RawConfig object to reach that
1509+
// root, at which point local references work going forwards.
1510+
if prefix != "" {
1511+
parts := strings.Split(prefix, ".") // "node_pool.N." -> ["node_pool" "N", ""]
1512+
npIndex, err := strconv.Atoi(parts[1])
1513+
if err != nil { // no error return from expander
1514+
panic(fmt.Errorf("unexpected format for node pool path prefix: %w. value: %v", err, prefix))
1515+
}
1516+
1517+
rawConfigNPRoot = rawConfigNPRoot.GetAttr("node_pool").Index(cty.NumberIntVal(int64(npIndex)))
1518+
}
1519+
1520+
if vNC := rawConfigNPRoot.GetAttr("node_config"); vNC.LengthInt() > 0 {
1521+
if vKC := vNC.Index(cty.NumberIntVal(0)).GetAttr("kubelet_config"); vKC.LengthInt() > 0 {
1522+
v := vKC.Index(cty.NumberIntVal(0)).GetAttr("cpu_cfs_quota");
1523+
if v == cty.NullVal(cty.Bool) {
1524+
nc.KubeletConfig.CpuCfsQuota = true
1525+
} else if v.False() { // force-send explicit false to API
1526+
nc.KubeletConfig.ForceSendFields = append(nc.KubeletConfig.ForceSendFields, "CpuCfsQuota")
1527+
}
1528+
}
1529+
}
1530+
// end cpu_cfs_quota fix
14991531
}
15001532

15011533
if v, ok := nodeConfig["linux_node_config"]; ok {
@@ -1640,7 +1672,6 @@ func expandKubeletConfig(v interface{}) *container.NodeKubeletConfig {
16401672
}
16411673
if cpuCfsQuota, ok := cfg["cpu_cfs_quota"]; ok {
16421674
kConfig.CpuCfsQuota = cpuCfsQuota.(bool)
1643-
kConfig.ForceSendFields = append(kConfig.ForceSendFields, "CpuCfsQuota")
16441675
}
16451676
if cpuCfsQuotaPeriod, ok := cfg["cpu_cfs_quota_period"]; ok {
16461677
kConfig.CpuCfsQuotaPeriod = cpuCfsQuotaPeriod.(string)

mmv1/third_party/terraform/services/container/resource_container_cluster.go.tmpl

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

28742874
if v, ok := d.GetOk("node_pool_defaults"); ok {
28752875
cluster.NodePoolDefaults = expandNodePoolDefaults(v)
28762876
}
28772877

28782878
if v, ok := d.GetOk("node_config"); ok {
2879-
cluster.NodeConfig = expandNodeConfig(v)
2879+
cluster.NodeConfig = expandNodeConfig(d, "", v)
28802880
}
28812881

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

mmv1/third_party/terraform/services/container/resource_container_cluster_test.go.tmpl

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6803,6 +6803,42 @@ func TestAccContainerCluster_additional_pod_ranges_config_on_update(t *testing.T
68036803
})
68046804
}
68056805

6806+
func TestAccContainerCluster_withCpuCfsQuotaPool(t *testing.T) {
6807+
t.Parallel()
6808+
6809+
clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10))
6810+
npName := fmt.Sprintf("tf-test-cluster-nodepool-%s", acctest.RandString(t, 10))
6811+
networkName := acctest.BootstrapSharedTestNetwork(t, "gke-cluster")
6812+
subnetworkName := acctest.BootstrapSubnet(t, "gke-cluster", networkName)
6813+
6814+
acctest.VcrTest(t, resource.TestCase{
6815+
PreCheck: func() { acctest.AccTestPreCheck(t) },
6816+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
6817+
CheckDestroy: testAccCheckContainerClusterDestroyProducer(t),
6818+
Steps: []resource.TestStep{
6819+
{
6820+
Config: testAccContainerCluster_withCpuCfsQuotaPool(clusterName, npName, networkName, subnetworkName),
6821+
},
6822+
{
6823+
ResourceName: "google_container_cluster.with_kubelet_config",
6824+
ImportState: true,
6825+
ImportStateVerify: true,
6826+
ImportStateVerifyIgnore: []string{"deletion_protection"},
6827+
},
6828+
{
6829+
Config: testAccContainerCluster_withCpuCfsQuotaPool2(clusterName, npName, networkName, subnetworkName),
6830+
PlanOnly: true,
6831+
},
6832+
{
6833+
ResourceName: "google_container_cluster.with_kubelet_config",
6834+
ImportState: true,
6835+
ImportStateVerify: true,
6836+
ImportStateVerifyIgnore: []string{"deletion_protection"},
6837+
},
6838+
},
6839+
})
6840+
}
6841+
68066842
func testAccContainerCluster_masterAuthorizedNetworksDisabled(t *testing.T, resource_name string) resource.TestCheckFunc {
68076843
return func(s *terraform.State) error {
68086844
rs, ok := s.RootModule().Resources[resource_name]
@@ -14974,3 +15010,67 @@ resource "google_container_cluster" "with_kubelet_config" {
1497415010
}
1497515011
`, clusterName, networkName, subnetworkName, cpuManagerPolicy, memoryManagerPolicy, topologyManagerPolicy, topologyManagerScope)
1497615012
}
15013+
15014+
func testAccContainerCluster_withCpuCfsQuotaPool(clusterName, npName, networkName, subnetworkName string) string {
15015+
return fmt.Sprintf(`
15016+
resource "google_container_cluster" "with_kubelet_config" {
15017+
name = %q
15018+
location = "us-central1-a"
15019+
network = %q
15020+
subnetwork = %q
15021+
deletion_protection = false
15022+
15023+
node_pool {
15024+
name = "%s-1"
15025+
initial_node_count = 1
15026+
node_config {
15027+
kubelet_config {
15028+
# cpu_cfs_quota = true
15029+
}
15030+
}
15031+
}
15032+
15033+
node_pool {
15034+
name = "%s-2"
15035+
initial_node_count = 1
15036+
node_config {
15037+
kubelet_config {
15038+
cpu_cfs_quota = false
15039+
}
15040+
}
15041+
}
15042+
}
15043+
`, clusterName, networkName, subnetworkName, npName, npName)
15044+
}
15045+
15046+
func testAccContainerCluster_withCpuCfsQuotaPool2(clusterName, npName, networkName, subnetworkName string) string {
15047+
return fmt.Sprintf(`
15048+
resource "google_container_cluster" "with_kubelet_config" {
15049+
name = %q
15050+
location = "us-central1-a"
15051+
network = %q
15052+
subnetwork = %q
15053+
deletion_protection = false
15054+
15055+
node_pool {
15056+
name = "%s-1"
15057+
initial_node_count = 1
15058+
node_config {
15059+
kubelet_config {
15060+
cpu_cfs_quota = true
15061+
}
15062+
}
15063+
}
15064+
15065+
node_pool {
15066+
name = "%s-2"
15067+
initial_node_count = 1
15068+
node_config {
15069+
kubelet_config {
15070+
cpu_cfs_quota = false
15071+
}
15072+
}
15073+
}
15074+
}
15075+
`, clusterName, networkName, subnetworkName, npName, npName)
15076+
}

mmv1/third_party/terraform/services/container/resource_container_node_pool.go.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ func expandNodePool(d *schema.ResourceData, prefix string) (*container.NodePool,
11361136
np := &container.NodePool{
11371137
Name: name,
11381138
InitialNodeCount: int64(nodeCount),
1139-
Config: expandNodeConfig(d.Get(prefix + "node_config")),
1139+
Config: expandNodeConfig(d, prefix, d.Get(prefix+"node_config")),
11401140
Locations: locations,
11411141
Version: d.Get(prefix + "version").(string),
11421142
NetworkConfig: expandNodeNetworkConfig(d.Get(prefix + "network_config")),

0 commit comments

Comments
 (0)