@@ -31,6 +31,22 @@ import (
3131
3232const createContainerSubdirectoryForProcessDumpSuffix = "{container_id}"
3333
34+ // Sentinel errors returned by ConvertCPUAffinity.
35+ var (
36+ // ErrCPUAffinityMultipleGroupsNotSupported is returned when multiple processor-group
37+ // affinity entries are requested on a host older than Windows Server 2022 (build 20348),
38+ // which does not support multi-group affinity for job object silos.
39+ // On Windows Server 2022+, multiple processor groups are fully supported.
40+ ErrCPUAffinityMultipleGroupsNotSupported = errors .New ("cpu affinity with multiple processor groups requires Windows Server 2022 or later" )
41+ // ErrCPUAffinityNonZeroGroupNotSupported is returned when a non-zero processor group is
42+ // requested on a host older than Windows Server 2022 (build 20348).
43+ // On Windows Server 2022+, non-zero processor groups are fully supported.
44+ ErrCPUAffinityNonZeroGroupNotSupported = errors .New ("cpu affinity with a non-zero processor group requires Windows Server 2022 or later" )
45+ // ErrCPUAffinityMaskZero is returned when an affinity entry has a zero bitmask,
46+ // which would select no processors and is always invalid.
47+ ErrCPUAffinityMaskZero = errors .New ("cpu affinity mask must be non-zero" )
48+ )
49+
3450// A simple wrapper struct around the container mount configs that should be added to the
3551// container.
3652type mountsConfig struct {
@@ -97,26 +113,36 @@ func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mount
97113// ConvertCPUAffinity handles the logic of converting and validating the container's CPU affinity
98114// specified in the OCI spec to what HCS expects.
99115//
100- // Returns the CPU affinity bitmask (0 if not specified) and any validation error.
101- // Phase 2 limitations:
102- // - Multiple affinity entries are rejected
103- // - Non-zero processor groups are rejected
104- func ConvertCPUAffinity (spec * specs.Spec ) (uint64 , error ) {
116+ // Returns the validated affinity entries (nil if not specified) and any validation error.
117+ // Multiple processor groups and non-zero group numbers require Windows Server 2022
118+ // (build 20348) or later; on older hosts only a single entry for group 0 is accepted.
119+ func ConvertCPUAffinity (spec * specs.Spec ) ([]specs.WindowsCPUGroupAffinity , error ) {
105120 if spec .Windows == nil || spec .Windows .Resources == nil || spec .Windows .Resources .CPU == nil || len (spec .Windows .Resources .CPU .Affinity ) == 0 {
106- return 0 , nil
121+ return nil , nil
107122 }
108123
109124 affinity := spec .Windows .Resources .CPU .Affinity
110- if len (affinity ) != 1 {
111- return 0 , fmt .Errorf ("cpu affinity with multiple processor groups is not supported" )
112- }
113- if affinity [0 ].Group != 0 {
114- return 0 , fmt .Errorf ("cpu affinity processor group %d is not supported" , affinity [0 ].Group )
125+
126+ // Zero masks are never valid regardless of OS version.
127+ for i , a := range affinity {
128+ if a .Mask == 0 {
129+ return nil , fmt .Errorf ("%w: entry %d has zero mask" , ErrCPUAffinityMaskZero , i )
130+ }
115131 }
116- if affinity [0 ].Mask == 0 {
117- return 0 , fmt .Errorf ("cpu affinity mask must be non-zero" )
132+
133+ // Determine whether multi-group features are needed: either multiple entries,
134+ // or a single entry targeting a non-zero processor group.
135+ multiGroup := len (affinity ) > 1 || affinity [0 ].Group != 0
136+
137+ // Multiple processor groups are only supported on Windows Server 2022+.
138+ if multiGroup && osversion .Build () < osversion .LTSC2022 {
139+ if len (affinity ) > 1 {
140+ return nil , fmt .Errorf ("%w: %d entries" , ErrCPUAffinityMultipleGroupsNotSupported , len (affinity ))
141+ }
142+ return nil , fmt .Errorf ("%w: group %d" , ErrCPUAffinityNonZeroGroupNotSupported , affinity [0 ].Group )
118143 }
119- return affinity [0 ].Mask , nil
144+
145+ return affinity , nil
120146}
121147
122148// ConvertCPULimits handles the logic of converting and validating the containers CPU limits
@@ -209,6 +235,7 @@ func createWindowsContainerDocument(ctx context.Context, coi *createOptionsInter
209235 return nil , nil , err
210236 }
211237
238+ // Validate and retrieve CPU affinity from the spec.
212239 cpuAffinity , err := ConvertCPUAffinity (coi .Spec )
213240 if err != nil {
214241 return nil , nil , err
@@ -262,12 +289,22 @@ func createWindowsContainerDocument(ctx context.Context, coi *createOptionsInter
262289 v1 .ProcessorMaximum = int64 (cpuLimit )
263290 v1 .ProcessorWeight = uint64 (cpuWeight )
264291
265- v2Container .Processor = & hcsschema.Processor {
266- Count : cpuCount ,
267- Maximum : cpuLimit ,
268- Weight : cpuWeight ,
269- Affinity : cpuAffinity ,
292+ v2Processor := & hcsschema.Processor {
293+ Count : cpuCount ,
294+ Maximum : cpuLimit ,
295+ Weight : cpuWeight ,
296+ }
297+ if len (cpuAffinity ) > 0 {
298+ groupAffs := make ([]hcsschema.ProcessorGroupAffinity , len (cpuAffinity ))
299+ for i , a := range cpuAffinity {
300+ groupAffs [i ] = hcsschema.ProcessorGroupAffinity {
301+ Mask : a .Mask ,
302+ Group : uint16 (a .Group ),
303+ }
304+ }
305+ v2Processor .GroupAffinities = groupAffs
270306 }
307+ v2Container .Processor = v2Processor
271308
272309 // Memory Resources
273310 memoryMaxInMB := oci .ParseAnnotationsMemory (ctx , coi .Spec , annotations .ContainerMemorySizeInMB , 0 )
0 commit comments