Skip to content

Commit 7ea66ea

Browse files
committed
feat: add kubelet configuration support for Azure node policy
This adds kubelet as a nested attribute under azure in the node policy schema, exposing fields like cpu_manager_policy, cpu_cfs_quota, image_gc_*, topology_manager_policy, allowed_unsafe_sysctls, container_log_max_*, and pod_pids_limit. Tests cover model validation and schema regression for all kubelet sub-attributes
1 parent 7044ec4 commit 7ea66ea

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

internal/provider/node_policy.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,53 @@ func (r *NodePolicyResource) Schema(ctx context.Context, req resource.SchemaRequ
616616
Description: "Maximum number of pods per node",
617617
Optional: true,
618618
},
619+
"kubelet": schema.SingleNestedAttribute{
620+
Description: "Azure kubelet configuration overrides",
621+
Optional: true,
622+
Attributes: map[string]schema.Attribute{
623+
"cpu_manager_policy": schema.StringAttribute{
624+
Description: "CPU manager policy (None, static)",
625+
Optional: true,
626+
},
627+
"cpu_cfs_quota": schema.BoolAttribute{
628+
Description: "Enable CPU CFS quota enforcement",
629+
Optional: true,
630+
},
631+
"cpu_cfs_quota_period": schema.StringAttribute{
632+
Description: "CPU CFS quota period",
633+
Optional: true,
634+
},
635+
"image_gc_high_threshold_percent": schema.Int32Attribute{
636+
Description: "Image GC high threshold percent",
637+
Optional: true,
638+
},
639+
"image_gc_low_threshold_percent": schema.Int32Attribute{
640+
Description: "Image GC low threshold percent",
641+
Optional: true,
642+
},
643+
"topology_manager_policy": schema.StringAttribute{
644+
Description: "Topology manager policy",
645+
Optional: true,
646+
},
647+
"allowed_unsafe_sysctls": schema.ListAttribute{
648+
Description: "List of allowed unsafe sysctls",
649+
Optional: true,
650+
ElementType: types.StringType,
651+
},
652+
"container_log_max_size": schema.StringAttribute{
653+
Description: "Maximum container log file size",
654+
Optional: true,
655+
},
656+
"container_log_max_files": schema.Int32Attribute{
657+
Description: "Maximum number of container log files",
658+
Optional: true,
659+
},
660+
"pod_pids_limit": schema.Int64Attribute{
661+
Description: "Maximum number of PIDs per pod",
662+
Optional: true,
663+
},
664+
},
665+
},
619666
},
620667
},
621668
// Raw Karpenter specs

internal/provider/node_policy_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ func TestNodePolicyResourceModel(t *testing.T) {
154154
FipsMode: types.StringValue("Disabled"),
155155
Tags: types.MapValueMust(types.StringType, map[string]attr.Value{"Environment": types.StringValue("production")}),
156156
MaxPods: types.Int32Value(110),
157+
Kubelet: &AzureKubeletConfiguration{
158+
CpuManagerPolicy: types.StringValue("static"),
159+
CpuCfsQuota: types.BoolValue(true),
160+
CpuCfsQuotaPeriod: types.StringValue("100ms"),
161+
ImageGcHighThresholdPercent: types.Int32Value(85),
162+
ImageGcLowThresholdPercent: types.Int32Value(70),
163+
TopologyManagerPolicy: types.StringValue("restricted"),
164+
AllowedUnsafeSysctls: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("net.ipv4.tcp_syncookies")}),
165+
ContainerLogMaxSize: types.StringValue("50Mi"),
166+
ContainerLogMaxFiles: types.Int32Value(5),
167+
PodPidsLimit: types.Int64Value(4096),
168+
},
157169
}
158170

159171
if azureConfig.VnetSubnetId.ValueString() == "" {
@@ -168,6 +180,18 @@ func TestNodePolicyResourceModel(t *testing.T) {
168180
if azureConfig.MaxPods.ValueInt32() != 110 {
169181
t.Errorf("Expected max_pods to be 110, got %d", azureConfig.MaxPods.ValueInt32())
170182
}
183+
if azureConfig.Kubelet == nil {
184+
t.Fatal("Expected Kubelet to be non-nil")
185+
}
186+
if azureConfig.Kubelet.CpuManagerPolicy.ValueString() != "static" {
187+
t.Errorf("Expected CpuManagerPolicy to be 'static', got %s", azureConfig.Kubelet.CpuManagerPolicy.ValueString())
188+
}
189+
if !azureConfig.Kubelet.CpuCfsQuota.ValueBool() {
190+
t.Error("Expected CpuCfsQuota to be true")
191+
}
192+
if azureConfig.Kubelet.PodPidsLimit.ValueInt64() != 4096 {
193+
t.Errorf("Expected PodPidsLimit to be 4096, got %d", azureConfig.Kubelet.PodPidsLimit.ValueInt64())
194+
}
171195
})
172196

173197
// Test RawKarpenterSpec
@@ -1014,6 +1038,10 @@ func TestNodePolicyResourceModel(t *testing.T) {
10141038
})
10151039
}
10161040

1041+
// singleNestedSchema is a local alias to allow type-asserting schema.Attribute
1042+
// to access nested Attributes without importing internal framework packages.
1043+
type singleNestedSchema = schema.SingleNestedAttribute
1044+
10171045
func validateNodePolicySchema(t *testing.T, schema schema.Schema) {
10181046
// Validate required attributes
10191047
requiredAttrs := []string{"name"}
@@ -1070,4 +1098,49 @@ func validateNodePolicySchema(t *testing.T, schema schema.Schema) {
10701098
if _, exists := schema.Attributes["azure"]; !exists {
10711099
t.Error("Azure configuration not found in schema")
10721100
}
1101+
1102+
// Validate azure nested attributes include kubelet (regression test for missing kubelet schema bug)
1103+
azureRaw, ok := schema.Attributes["azure"]
1104+
if !ok {
1105+
t.Fatal("azure attribute not found in schema")
1106+
}
1107+
azureSingle, ok := azureRaw.(singleNestedSchema)
1108+
if !ok {
1109+
t.Fatal("azure attribute is not a SingleNestedAttribute")
1110+
}
1111+
azureKubeletFields := []string{
1112+
"kubelet",
1113+
"vnet_subnet_id",
1114+
"os_disk_size_gb",
1115+
"image_family",
1116+
"fips_mode",
1117+
"tags",
1118+
"max_pods",
1119+
}
1120+
for _, field := range azureKubeletFields {
1121+
if _, exists := azureSingle.Attributes[field]; !exists {
1122+
t.Errorf("azure schema is missing '%s' attribute", field)
1123+
}
1124+
}
1125+
1126+
// Validate kubelet sub-attributes
1127+
kubeletRaw, ok := azureSingle.Attributes["kubelet"]
1128+
if !ok {
1129+
t.Fatal("azure schema is missing 'kubelet' attribute")
1130+
}
1131+
kubeletSingle, ok := kubeletRaw.(singleNestedSchema)
1132+
if !ok {
1133+
t.Fatal("azure.kubelet is not a SingleNestedAttribute")
1134+
}
1135+
kubeletFields := []string{
1136+
"cpu_manager_policy", "cpu_cfs_quota", "cpu_cfs_quota_period",
1137+
"image_gc_high_threshold_percent", "image_gc_low_threshold_percent",
1138+
"topology_manager_policy", "allowed_unsafe_sysctls",
1139+
"container_log_max_size", "container_log_max_files", "pod_pids_limit",
1140+
}
1141+
for _, field := range kubeletFields {
1142+
if _, exists := kubeletSingle.Attributes[field]; !exists {
1143+
t.Errorf("azure.kubelet schema is missing '%s' attribute", field)
1144+
}
1145+
}
10731146
}

0 commit comments

Comments
 (0)