Skip to content

Commit 169c6ef

Browse files
committed
Set default CPU period and quota for LCOW containers
For LCOW containers, if the CPU Period and Quota are not set in the Linux Resource Limits, then we set the default values for the same. This commit adds the change. Signed-off-by: Harsh Rawat <[email protected]>
1 parent f042cd0 commit 169c6ef

File tree

2 files changed

+316
-0
lines changed

2 files changed

+316
-0
lines changed

internal/hcsoci/hcsdoc_lcow.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ func createLCOWSpec(ctx context.Context, coi *createOptionsInternal) (*specs.Spe
3838
// Hooks are not supported (they should be run in the host)
3939
spec.Hooks = nil
4040

41+
// Set default CPU period and quota if not set for LCOW containers.
42+
if spec.Linux != nil &&
43+
spec.Linux.Resources != nil &&
44+
spec.Linux.Resources.CPU != nil {
45+
46+
if spec.Linux.Resources.CPU.Period != nil && *spec.Linux.Resources.CPU.Period == 0 {
47+
*spec.Linux.Resources.CPU.Period = 100000 // Default CPU period
48+
}
49+
if spec.Linux.Resources.CPU.Quota != nil && *spec.Linux.Resources.CPU.Quota == 0 {
50+
*spec.Linux.Resources.CPU.Quota = -1 // No CPU limit
51+
}
52+
}
53+
4154
// Clear unsupported features
4255
spec.Linux.CgroupsPath = "" // GCS controls its cgroups hierarchy on its own.
4356
if spec.Linux.Resources != nil {
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
//go:build windows
2+
3+
package hcsoci
4+
5+
import (
6+
"context"
7+
"reflect"
8+
"testing"
9+
10+
"github.com/Microsoft/hcsshim/internal/schemaversion"
11+
12+
specs "github.com/opencontainers/runtime-spec/specs-go"
13+
)
14+
15+
// baseSpec returns a minimal valid spec to reduce boilerplate in tests.
16+
func baseSpec() *specs.Spec {
17+
return &specs.Spec{
18+
Linux: &specs.Linux{
19+
Resources: &specs.LinuxResources{
20+
CPU: &specs.LinuxCPU{},
21+
},
22+
},
23+
Windows: &specs.Windows{},
24+
Annotations: map[string]string{"key": "original"},
25+
}
26+
}
27+
28+
// TestCreateLCOWSpec_CPUDefaults tests the logic for applying default CPU Period and Quota values.
29+
func TestCreateLCOWSpec_CPUDefaults(t *testing.T) {
30+
ctx := context.Background()
31+
32+
tests := []struct {
33+
name string
34+
setup func() *specs.Spec
35+
wantPeriod *uint64 // Using pointer to differentiate between 0 and nil
36+
wantQuota *int64
37+
}{
38+
{
39+
name: "Defaults applied when explicit 0",
40+
setup: func() *specs.Spec {
41+
s := baseSpec()
42+
s.Linux.Resources.CPU.Period = ptrUint64(0)
43+
s.Linux.Resources.CPU.Quota = ptrInt64(0)
44+
return s
45+
},
46+
wantPeriod: ptrUint64(100000),
47+
wantQuota: ptrInt64(-1),
48+
},
49+
{
50+
name: "Defaults ignored when non-zero",
51+
setup: func() *specs.Spec {
52+
s := baseSpec()
53+
s.Linux.Resources.CPU.Period = ptrUint64(50000)
54+
s.Linux.Resources.CPU.Quota = ptrInt64(20000)
55+
return s
56+
},
57+
wantPeriod: ptrUint64(50000),
58+
wantQuota: ptrInt64(20000),
59+
},
60+
{
61+
name: "Defaults ignored when omitted (nil)",
62+
// The code checks `if ptr != nil && *ptr == 0`, so nil inputs remain nil.
63+
setup: func() *specs.Spec {
64+
s := baseSpec()
65+
s.Linux.Resources.CPU.Period = nil
66+
s.Linux.Resources.CPU.Quota = nil
67+
return s
68+
},
69+
wantPeriod: nil,
70+
wantQuota: nil,
71+
},
72+
}
73+
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
inputSpec := tt.setup()
77+
coi := &createOptionsInternal{CreateOptions: &CreateOptions{Spec: inputSpec}}
78+
79+
result, err := createLCOWSpec(ctx, coi)
80+
if err != nil {
81+
t.Fatalf("unexpected error: %v", err)
82+
}
83+
84+
// Validate Period
85+
gotPeriod := result.Linux.Resources.CPU.Period
86+
if tt.wantPeriod == nil {
87+
if gotPeriod != nil {
88+
t.Errorf("Period: want nil, got %d", *gotPeriod)
89+
}
90+
} else {
91+
if gotPeriod == nil || *gotPeriod != *tt.wantPeriod {
92+
t.Errorf("Period: want %d, got %v", *tt.wantPeriod, gotPeriod)
93+
}
94+
}
95+
96+
// Validate Quota
97+
gotQuota := result.Linux.Resources.CPU.Quota
98+
if tt.wantQuota == nil {
99+
if gotQuota != nil {
100+
t.Errorf("Quota: want nil, got %d", *gotQuota)
101+
}
102+
} else {
103+
if gotQuota == nil || *gotQuota != *tt.wantQuota {
104+
t.Errorf("Quota: want %d, got %v", *tt.wantQuota, gotQuota)
105+
}
106+
}
107+
})
108+
}
109+
}
110+
111+
// TestCreateLCOWSpec_ResourcesDeepCopy tests that all fields in Resources are correctly deep-copied.
112+
func TestCreateLCOWSpec_ResourcesDeepCopy(t *testing.T) {
113+
// Goal: Verify that other CPU fields and Memory fields are correctly copied
114+
// and are not lost or corrupted during the process.
115+
ctx := context.Background()
116+
s := baseSpec()
117+
118+
// Populate extensive fields
119+
s.Linux.Resources.CPU = &specs.LinuxCPU{
120+
Shares: ptrUint64(1024),
121+
RealtimePeriod: ptrUint64(1000),
122+
RealtimeRuntime: ptrInt64(500),
123+
Cpus: "0-1",
124+
Mems: "0",
125+
}
126+
s.Linux.Resources.Memory = &specs.LinuxMemory{
127+
Limit: ptrInt64(2048),
128+
Reservation: ptrInt64(1024),
129+
Swap: ptrInt64(4096),
130+
Swappiness: ptrUint64(60),
131+
}
132+
133+
coi := &createOptionsInternal{CreateOptions: &CreateOptions{Spec: s}}
134+
result, err := createLCOWSpec(ctx, coi)
135+
if err != nil {
136+
t.Fatalf("unexpected error: %v", err)
137+
}
138+
139+
// Validate CPU fields
140+
resCPU := result.Linux.Resources.CPU
141+
if *resCPU.Shares != 1024 {
142+
t.Errorf("CPU Shares mismatch: got %v", *resCPU.Shares)
143+
}
144+
if *resCPU.RealtimePeriod != 1000 {
145+
t.Errorf("CPU RealtimePeriod mismatch: got %v", *resCPU.RealtimePeriod)
146+
}
147+
if *resCPU.RealtimeRuntime != 500 {
148+
t.Errorf("CPU RealtimeRuntime mismatch: got %v", *resCPU.RealtimeRuntime)
149+
}
150+
if resCPU.Cpus != "0-1" {
151+
t.Errorf("CPU Cpus mismatch: got %v", resCPU.Cpus)
152+
}
153+
if resCPU.Mems != "0" {
154+
t.Errorf("CPU Mems mismatch: got %v", resCPU.Mems)
155+
}
156+
157+
// Validate Memory fields
158+
resMem := result.Linux.Resources.Memory
159+
if *resMem.Limit != 2048 {
160+
t.Errorf("Memory Limit mismatch: got %v", *resMem.Limit)
161+
}
162+
if *resMem.Reservation != 1024 {
163+
t.Errorf("Memory Reservation mismatch: got %v", *resMem.Reservation)
164+
}
165+
if *resMem.Swap != 4096 {
166+
t.Errorf("Memory Swap mismatch: got %v", *resMem.Swap)
167+
}
168+
if *resMem.Swappiness != 60 {
169+
t.Errorf("Memory Swappiness mismatch: got %v", *resMem.Swappiness)
170+
}
171+
}
172+
173+
// TestCreateLCOWSpec_WindowsLogicMatrix tests various combinations of Windows struct fields.
174+
func TestCreateLCOWSpec_WindowsLogicMatrix(t *testing.T) {
175+
// Goal: Test Partial Windows structs.
176+
ctx := context.Background()
177+
178+
tests := []struct {
179+
name string
180+
inputWindows *specs.Windows
181+
expectWindows bool
182+
expectNet string
183+
expectDevs int
184+
}{
185+
{
186+
name: "Nil Windows",
187+
inputWindows: nil,
188+
expectWindows: false,
189+
},
190+
{
191+
name: "Empty Windows",
192+
inputWindows: &specs.Windows{},
193+
expectWindows: false,
194+
},
195+
{
196+
name: "Network Only",
197+
inputWindows: &specs.Windows{
198+
Network: &specs.WindowsNetwork{NetworkNamespace: "host-ns"},
199+
},
200+
expectWindows: true,
201+
expectNet: "host-ns",
202+
},
203+
{
204+
name: "Devices Only",
205+
inputWindows: &specs.Windows{
206+
Devices: []specs.WindowsDevice{{ID: "gpu1"}},
207+
},
208+
expectWindows: true,
209+
expectDevs: 1,
210+
},
211+
}
212+
213+
for _, tt := range tests {
214+
t.Run(tt.name, func(t *testing.T) {
215+
s := baseSpec()
216+
s.Windows = tt.inputWindows
217+
coi := &createOptionsInternal{CreateOptions: &CreateOptions{Spec: s}}
218+
219+
res, err := createLCOWSpec(ctx, coi)
220+
if err != nil {
221+
t.Fatalf("error: %v", err)
222+
}
223+
224+
if !tt.expectWindows && res.Windows != nil {
225+
t.Errorf("Expected nil Windows, got: %+v", res.Windows)
226+
}
227+
if tt.expectWindows && res.Windows == nil {
228+
t.Fatal("Expected Windows section, got nil")
229+
}
230+
if tt.expectNet != "" && (res.Windows.Network == nil || res.Windows.Network.NetworkNamespace != tt.expectNet) {
231+
t.Errorf("Network Namespace mismatch")
232+
}
233+
if res.Windows != nil && len(res.Windows.Devices) != tt.expectDevs {
234+
t.Errorf("Device count mismatch")
235+
}
236+
})
237+
}
238+
}
239+
240+
// TestCreateLCOWSpec_FieldSanitization tests that specific fields are cleared from the spec.
241+
func TestCreateLCOWSpec_FieldSanitization(t *testing.T) {
242+
// Goal: Ensure specific fields are cleared but valid ones preserved.
243+
ctx := context.Background()
244+
s := baseSpec()
245+
246+
// Fields to remove
247+
s.Hooks = &specs.Hooks{CreateContainer: []specs.Hook{{Path: "bad"}}}
248+
s.Linux.CgroupsPath = "/bad/path"
249+
s.Linux.Resources.BlockIO = &specs.LinuxBlockIO{Weight: ptrUint16(10)}
250+
251+
coi := &createOptionsInternal{CreateOptions: &CreateOptions{Spec: s}}
252+
res, err := createLCOWSpec(ctx, coi)
253+
if err != nil {
254+
t.Fatalf("error: %v", err)
255+
}
256+
257+
if res.Hooks != nil {
258+
t.Error("Hooks not cleared")
259+
}
260+
if res.Linux.CgroupsPath != "" {
261+
t.Error("CgroupsPath not cleared")
262+
}
263+
if res.Linux.Resources.BlockIO != nil {
264+
t.Error("BlockIO not cleared")
265+
}
266+
}
267+
268+
// TestCreateLinuxContainerDocument_PopulatesFields tests that createLinuxContainerDocument populates fields correctly.
269+
func TestCreateLinuxContainerDocument_PopulatesFields(t *testing.T) {
270+
ctx := context.Background()
271+
272+
inputSpec := baseSpec()
273+
// Trigger defaults
274+
inputSpec.Linux.Resources.CPU.Period = ptrUint64(0)
275+
inputSpec.Linux.Resources.CPU.Quota = ptrInt64(0)
276+
277+
coi := &createOptionsInternal{CreateOptions: &CreateOptions{Spec: inputSpec}}
278+
guestRoot := "C:\\guest\\root"
279+
scratch := "C:\\scratch\\path"
280+
281+
doc, err := createLinuxContainerDocument(ctx, coi, guestRoot, scratch)
282+
if err != nil {
283+
t.Fatalf("createLinuxContainerDocument error: %v", err)
284+
}
285+
286+
// 1. Schema
287+
if !reflect.DeepEqual(doc.SchemaVersion, schemaversion.SchemaV21()) {
288+
t.Errorf("SchemaVersion mismatch")
289+
}
290+
// 2. Paths
291+
if doc.OciBundlePath != guestRoot || doc.ScratchDirPath != scratch {
292+
t.Errorf("Path mismatch")
293+
}
294+
// 3. Spec Defaults
295+
if *doc.OciSpecification.Linux.Resources.CPU.Period != 100000 {
296+
t.Errorf("Defaults not applied in wrapper")
297+
}
298+
}
299+
300+
// --- Helper Functions ---
301+
func ptrUint16(v uint16) *uint16 { return &v }
302+
func ptrUint64(v uint64) *uint64 { return &v }
303+
func ptrInt64(v int64) *int64 { return &v }

0 commit comments

Comments
 (0)