Skip to content

Commit 9cceb1c

Browse files
wenyangWeyang1
andauthored
jobcontainers: wire Windows CPU affinity to JobObject limits (microsoft#2603)
Signed-off-by: Weyang1 <weyang1@microsoft.com> Co-authored-by: Weyang1 <weyang1@microsoft.com>
1 parent 9bc19ab commit 9cceb1c

5 files changed

Lines changed: 145 additions & 0 deletions

File tree

internal/jobcontainers/oci.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package jobcontainers
44

55
import (
66
"context"
7+
"fmt"
78

89
"github.com/Microsoft/hcsshim/internal/hcsoci"
910
"github.com/Microsoft/hcsshim/internal/jobobject"
@@ -40,6 +41,21 @@ func specToLimits(ctx context.Context, cid string, s *specs.Spec) (*jobobject.Jo
4041
return nil, err
4142
}
4243

44+
var cpuAffinity uint64
45+
if s.Windows != nil && s.Windows.Resources != nil && s.Windows.Resources.CPU != nil && len(s.Windows.Resources.CPU.Affinity) > 0 {
46+
affinity := s.Windows.Resources.CPU.Affinity
47+
if len(affinity) != 1 {
48+
return nil, fmt.Errorf("cpu affinity with multiple processor groups is not supported")
49+
}
50+
if affinity[0].Group != 0 {
51+
return nil, fmt.Errorf("cpu affinity processor group %d is not supported", affinity[0].Group)
52+
}
53+
if affinity[0].Mask == 0 {
54+
return nil, fmt.Errorf("cpu affinity mask must be non-zero")
55+
}
56+
cpuAffinity = affinity[0].Mask
57+
}
58+
4359
realCPULimit, realCPUWeight := uint32(cpuLimit), uint32(cpuWeight)
4460
if cpuCount != 0 {
4561
// Job object API does not support "CPU count". Instead, we translate the notion of "count" into
@@ -61,6 +77,7 @@ func specToLimits(ctx context.Context, cid string, s *specs.Spec) (*jobobject.Jo
6177
return &jobobject.JobLimits{
6278
CPULimit: realCPULimit,
6379
CPUWeight: realCPUWeight,
80+
CPUAffinity: cpuAffinity,
6481
MaxIOPS: maxIops,
6582
MaxBandwidth: maxBandwidth,
6683
MemoryLimitInBytes: memLimitMB * memory.MiB,

internal/jobcontainers/oci_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//go:build windows
2+
3+
package jobcontainers
4+
5+
import (
6+
"context"
7+
"strings"
8+
"testing"
9+
10+
specs "github.com/opencontainers/runtime-spec/specs-go"
11+
)
12+
13+
func TestSpecToLimits_CPUAffinity_Group0MaskSet(t *testing.T) {
14+
s := &specs.Spec{
15+
Windows: &specs.Windows{
16+
Resources: &specs.WindowsResources{
17+
CPU: &specs.WindowsCPUResources{
18+
Affinity: []specs.WindowsCPUGroupAffinity{
19+
{Mask: 0x3, Group: 0},
20+
},
21+
},
22+
},
23+
},
24+
}
25+
26+
limits, err := specToLimits(context.Background(), "cid", s)
27+
if err != nil {
28+
t.Fatalf("specToLimits failed: %v", err)
29+
}
30+
if limits.CPUAffinity != 0x3 {
31+
t.Fatalf("unexpected cpu affinity: got %d want %d", limits.CPUAffinity, uint64(0x3))
32+
}
33+
}
34+
35+
func TestSpecToLimits_CPUAffinity_MultiGroupRejected(t *testing.T) {
36+
s := &specs.Spec{
37+
Windows: &specs.Windows{
38+
Resources: &specs.WindowsResources{
39+
CPU: &specs.WindowsCPUResources{
40+
Affinity: []specs.WindowsCPUGroupAffinity{
41+
{Mask: 0x1, Group: 0},
42+
{Mask: 0x1, Group: 1},
43+
},
44+
},
45+
},
46+
},
47+
}
48+
49+
_, err := specToLimits(context.Background(), "cid", s)
50+
if err == nil {
51+
t.Fatal("expected error for multiple affinity entries")
52+
}
53+
if !strings.Contains(err.Error(), "multiple processor groups") {
54+
t.Fatalf("unexpected error: %v", err)
55+
}
56+
}
57+
58+
func TestSpecToLimits_CPUAffinity_NonZeroGroupRejected(t *testing.T) {
59+
s := &specs.Spec{
60+
Windows: &specs.Windows{
61+
Resources: &specs.WindowsResources{
62+
CPU: &specs.WindowsCPUResources{
63+
Affinity: []specs.WindowsCPUGroupAffinity{
64+
{Mask: 0x1, Group: 1},
65+
},
66+
},
67+
},
68+
},
69+
}
70+
71+
_, err := specToLimits(context.Background(), "cid", s)
72+
if err == nil {
73+
t.Fatal("expected error for non-zero affinity group")
74+
}
75+
if !strings.Contains(err.Error(), "processor group") {
76+
t.Fatalf("unexpected error: %v", err)
77+
}
78+
}
79+
80+
func TestSpecToLimits_CPUAffinity_ZeroMaskRejected(t *testing.T) {
81+
s := &specs.Spec{
82+
Windows: &specs.Windows{
83+
Resources: &specs.WindowsResources{
84+
CPU: &specs.WindowsCPUResources{
85+
Affinity: []specs.WindowsCPUGroupAffinity{
86+
{Mask: 0, Group: 0},
87+
},
88+
},
89+
},
90+
},
91+
}
92+
93+
_, err := specToLimits(context.Background(), "cid", s)
94+
if err == nil {
95+
t.Fatal("expected error for zero affinity mask")
96+
}
97+
if !strings.Contains(err.Error(), "mask must be non-zero") {
98+
t.Fatalf("unexpected error: %v", err)
99+
}
100+
}

internal/jobobject/jobobject.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type JobObject struct {
3232
type JobLimits struct {
3333
CPULimit uint32
3434
CPUWeight uint32
35+
CPUAffinity uint64
3536
MemoryLimitInBytes uint64
3637
MaxIOPS int64
3738
MaxBandwidth int64

internal/jobobject/jobobject_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,27 @@ func TestSetMultipleExtendedLimits(t *testing.T) {
252252
}
253253
}
254254

255+
func TestSetResourceLimitsCPUAffinity(t *testing.T) {
256+
job, err := Create(context.Background(), nil)
257+
if err != nil {
258+
t.Fatal(err)
259+
}
260+
defer job.Close()
261+
262+
limits := &JobLimits{CPUAffinity: 0x3}
263+
if err := job.SetResourceLimits(limits); err != nil {
264+
t.Fatalf("failed to set resource limits with cpu affinity: %v", err)
265+
}
266+
267+
affinity, err := job.GetCPUAffinity()
268+
if err != nil {
269+
t.Fatalf("failed to query cpu affinity: %v", err)
270+
}
271+
if affinity != limits.CPUAffinity {
272+
t.Fatalf("unexpected cpu affinity: got %d want %d", affinity, limits.CPUAffinity)
273+
}
274+
}
275+
255276
func TestNoMoreProcessesMessageKill(t *testing.T) {
256277
// Test that we receive the no more processes in job message after killing all of
257278
// the processes in the job.

internal/jobobject/limits.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ func (job *JobObject) SetResourceLimits(limits *JobLimits) error {
3838
}
3939
}
4040

41+
if limits.CPUAffinity != 0 {
42+
if err := job.SetCPUAffinity(limits.CPUAffinity); err != nil {
43+
return fmt.Errorf("failed to set job object cpu affinity: %w", err)
44+
}
45+
}
46+
4147
if limits.MaxBandwidth != 0 || limits.MaxIOPS != 0 {
4248
if err := job.SetIOLimit(limits.MaxBandwidth, limits.MaxIOPS); err != nil {
4349
return fmt.Errorf("failed to set io limit on job object: %w", err)

0 commit comments

Comments
 (0)