1
2
3
4 package hcsoci
5
6 import (
7 "context"
8 "errors"
9 "fmt"
10 "math"
11 "path/filepath"
12 "regexp"
13 "strconv"
14 "strings"
15
16 "github.com/Microsoft/go-winio/pkg/fs"
17 specs "github.com/opencontainers/runtime-spec/specs-go"
18 "github.com/sirupsen/logrus"
19
20 "github.com/Microsoft/hcsshim/internal/guestpath"
21 "github.com/Microsoft/hcsshim/internal/hcs/schema1"
22 hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
23 "github.com/Microsoft/hcsshim/internal/layers"
24 "github.com/Microsoft/hcsshim/internal/log"
25 "github.com/Microsoft/hcsshim/internal/oci"
26 "github.com/Microsoft/hcsshim/internal/processorinfo"
27 "github.com/Microsoft/hcsshim/internal/uvm"
28 "github.com/Microsoft/hcsshim/internal/uvmfolder"
29 "github.com/Microsoft/hcsshim/internal/wclayer"
30 "github.com/Microsoft/hcsshim/osversion"
31 "github.com/Microsoft/hcsshim/pkg/annotations"
32 )
33
34 const createContainerSubdirectoryForProcessDumpSuffix = "{container_id}"
35
36
37
38 type mountsConfig struct {
39 mdsv1 []schema1.MappedDir
40 mpsv1 []schema1.MappedPipe
41 mdsv2 []hcsschema.MappedDirectory
42 mpsv2 []hcsschema.MappedPipe
43 }
44
45 func createMountsConfig(ctx context.Context, coi *createOptionsInternal) (*mountsConfig, error) {
46
47
48 var config mountsConfig
49 for _, mount := range coi.Spec.Mounts {
50 if uvm.IsPipe(mount.Source) {
51 src, dst := uvm.GetContainerPipeMapping(coi.HostingSystem, mount)
52 config.mpsv1 = append(config.mpsv1, schema1.MappedPipe{HostPath: src, ContainerPipeName: dst})
53 config.mpsv2 = append(config.mpsv2, hcsschema.MappedPipe{HostPath: src, ContainerPipeName: dst})
54 } else {
55 readOnly := false
56 for _, o := range mount.Options {
57 if strings.ToLower(o) == "ro" {
58 readOnly = true
59 }
60 }
61 mdv1 := schema1.MappedDir{HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: readOnly}
62 mdv2 := hcsschema.MappedDirectory{ContainerPath: mount.Destination, ReadOnly: readOnly}
63 if coi.HostingSystem == nil {
64
65
66
67
68 src, err := fs.ResolvePath(mount.Source)
69 if err != nil {
70 return nil, fmt.Errorf("failed to resolve path for mount source %q: %s", mount.Source, err)
71 }
72 mdv2.HostPath = src
73 } else if mount.Type == MountTypeVirtualDisk || mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeExtensibleVirtualDisk {
74
75
76 continue
77 } else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
78
79 mdv2.HostPath = convertToWCOWSandboxMountPath(mount.Source)
80 } else {
81
82 uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(ctx, mount.Source, readOnly)
83 if err != nil {
84 return nil, err
85 }
86 mdv2.HostPath = uvmPath
87 }
88 config.mdsv1 = append(config.mdsv1, mdv1)
89 config.mdsv2 = append(config.mdsv2, mdv2)
90 }
91 }
92 config.mdsv2 = append(config.mdsv2, coi.windowsAdditionalMounts...)
93 return &config, nil
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 func ConvertCPULimits(ctx context.Context, cid string, spec *specs.Spec, maxCPUCount int32) (int32, int32, int32, error) {
114 cpuNumSet := 0
115 cpuCount := oci.ParseAnnotationsCPUCount(ctx, spec, annotations.ContainerProcessorCount, 0)
116 if cpuCount > 0 {
117 cpuNumSet++
118 }
119
120 cpuLimit := oci.ParseAnnotationsCPULimit(ctx, spec, annotations.ContainerProcessorLimit, 0)
121 if cpuLimit > 0 {
122 cpuNumSet++
123 }
124
125 cpuWeight := oci.ParseAnnotationsCPUWeight(ctx, spec, annotations.ContainerProcessorWeight, 0)
126 if cpuWeight > 0 {
127 cpuNumSet++
128 }
129
130 if cpuNumSet > 1 {
131 return 0, 0, 0, fmt.Errorf("invalid spec - Windows Container CPU Count: '%d', Limit: '%d', and Weight: '%d' are mutually exclusive", cpuCount, cpuLimit, cpuWeight)
132 } else if cpuNumSet == 1 {
133 cpuCount = NormalizeProcessorCount(ctx, cid, cpuCount, maxCPUCount)
134 }
135 return cpuCount, cpuLimit, cpuWeight, nil
136 }
137
138
139
140
141 func createWindowsContainerDocument(ctx context.Context, coi *createOptionsInternal) (*schema1.ContainerConfig, *hcsschema.Container, error) {
142 log.G(ctx).Debug("hcsshim: CreateHCSContainerDocument")
143
144
145 if coi.Spec == nil {
146 return nil, nil, fmt.Errorf("cannot create HCS container document - OCI spec is missing")
147 }
148
149 if coi.Spec.Windows == nil {
150 return nil, nil, fmt.Errorf("cannot create HCS container document - OCI spec Windows section is missing ")
151 }
152
153 v1 := &schema1.ContainerConfig{
154 SystemType: "Container",
155 Name: coi.actualID,
156 Owner: coi.actualOwner,
157 HvPartition: false,
158 IgnoreFlushesDuringBoot: coi.Spec.Windows.IgnoreFlushesDuringBoot,
159 }
160
161
162
163 v2Container := &hcsschema.Container{Storage: &hcsschema.Storage{}}
164
165
166 if coi.Spec.Windows.LayerFolders == nil || len(coi.Spec.Windows.LayerFolders) < 2 {
167 return nil, nil, fmt.Errorf("invalid spec - not enough layer folders supplied")
168 }
169
170 if coi.Spec.Hostname != "" {
171 v1.HostName = coi.Spec.Hostname
172 v2Container.GuestOs = &hcsschema.GuestOs{HostName: coi.Spec.Hostname}
173 }
174
175 var (
176 uvmCPUCount int32
177 hostCPUCount = processorinfo.ProcessorCount()
178 maxCPUCount = hostCPUCount
179 )
180
181 if coi.HostingSystem != nil {
182 uvmCPUCount = coi.HostingSystem.ProcessorCount()
183 maxCPUCount = uvmCPUCount
184 }
185
186 cpuCount, cpuLimit, cpuWeight, err := ConvertCPULimits(ctx, coi.ID, coi.Spec, maxCPUCount)
187 if err != nil {
188 return nil, nil, err
189 }
190
191 if coi.HostingSystem != nil && coi.ScaleCPULimitsToSandbox && cpuLimit > 0 {
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216 newCPULimit := cpuLimit * hostCPUCount / uvmCPUCount
217
218
219
220
221 if newCPULimit < 1 {
222 newCPULimit = 1
223 } else if newCPULimit > 10000 {
224 newCPULimit = 10000
225 }
226 log.G(ctx).WithFields(logrus.Fields{
227 "hostCPUCount": hostCPUCount,
228 "uvmCPUCount": uvmCPUCount,
229 "oldCPULimit": cpuLimit,
230 "newCPULimit": newCPULimit,
231 }).Info("rescaling CPU limit for UVM sandbox")
232 cpuLimit = newCPULimit
233 }
234
235 v1.ProcessorCount = uint32(cpuCount)
236 v1.ProcessorMaximum = int64(cpuLimit)
237 v1.ProcessorWeight = uint64(cpuWeight)
238
239 v2Container.Processor = &hcsschema.Processor{
240 Count: cpuCount,
241 Maximum: cpuLimit,
242 Weight: cpuWeight,
243 }
244
245
246 memoryMaxInMB := oci.ParseAnnotationsMemory(ctx, coi.Spec, annotations.ContainerMemorySizeInMB, 0)
247 if memoryMaxInMB > 0 {
248 v1.MemoryMaximumInMB = int64(memoryMaxInMB)
249 v2Container.Memory = &hcsschema.Memory{
250 SizeInMB: memoryMaxInMB,
251 }
252 }
253
254
255 storageBandwidthMax := oci.ParseAnnotationsStorageBps(ctx, coi.Spec, annotations.ContainerStorageQoSBandwidthMaximum, 0)
256 storageIopsMax := oci.ParseAnnotationsStorageIops(ctx, coi.Spec, annotations.ContainerStorageQoSIopsMaximum, 0)
257 if storageBandwidthMax > 0 || storageIopsMax > 0 {
258 v1.StorageBandwidthMaximum = uint64(storageBandwidthMax)
259 v1.StorageIOPSMaximum = uint64(storageIopsMax)
260 v2Container.Storage.QoS = &hcsschema.StorageQoS{
261 BandwidthMaximum: storageBandwidthMax,
262 IopsMaximum: storageIopsMax,
263 }
264 }
265
266
267 if coi.Spec.Windows.Network != nil {
268 v2Container.Networking = &hcsschema.Networking{}
269
270 v1.EndpointList = coi.Spec.Windows.Network.EndpointList
271
272 v2Container.Networking.Namespace = coi.actualNetworkNamespace
273
274 v1.AllowUnqualifiedDNSQuery = coi.Spec.Windows.Network.AllowUnqualifiedDNSQuery
275 v2Container.Networking.AllowUnqualifiedDnsQuery = v1.AllowUnqualifiedDNSQuery
276
277 if coi.Spec.Windows.Network.DNSSearchList != nil {
278 v1.DNSSearchList = strings.Join(coi.Spec.Windows.Network.DNSSearchList, ",")
279 v2Container.Networking.DnsSearchList = v1.DNSSearchList
280 }
281
282 v1.NetworkSharedContainerName = coi.Spec.Windows.Network.NetworkSharedContainerName
283 v2Container.Networking.NetworkSharedContainerName = v1.NetworkSharedContainerName
284 }
285
286 if cs, ok := coi.Spec.Windows.CredentialSpec.(string); ok {
287 v1.Credentials = cs
288
289
290
291 if coi.ccgState != nil {
292 v2Container.ContainerCredentialGuard = coi.ccgState
293 }
294 }
295
296 if coi.Spec.Root == nil {
297 return nil, nil, fmt.Errorf("spec is invalid - root isn't populated")
298 }
299
300 if coi.Spec.Root.Readonly {
301 return nil, nil, fmt.Errorf(`invalid container spec - readonly is not supported for Windows containers`)
302 }
303
304
305 v1.LayerFolderPath = coi.Spec.Windows.LayerFolders[len(coi.Spec.Windows.LayerFolders)-1]
306
307 if coi.isV2Argon() || coi.isV1Argon() {
308
309 const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}(|\\)$`
310 if matched, err := regexp.MatchString(volumeGUIDRegex, coi.Spec.Root.Path); !matched || err != nil {
311 return nil, nil, fmt.Errorf(`invalid container spec - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, coi.Spec.Root.Path)
312 }
313 if coi.Spec.Root.Path[len(coi.Spec.Root.Path)-1] != '\\' {
314 coi.Spec.Root.Path += `\`
315 }
316 v1.VolumePath = coi.Spec.Root.Path[:len(coi.Spec.Root.Path)-1]
317 v2Container.Storage.Path = coi.Spec.Root.Path
318 } else if coi.isV1Xenon() {
319
320 v1.HvPartition = true
321 if coi.Spec == nil || coi.Spec.Windows == nil || coi.Spec.Windows.HyperV == nil {
322 return nil, nil, fmt.Errorf(`invalid container spec - Spec.Windows.HyperV is nil`)
323 }
324 if coi.Spec.Windows.HyperV.UtilityVMPath != "" {
325
326 v1.HvRuntime = &schema1.HvRuntime{ImagePath: coi.Spec.Windows.HyperV.UtilityVMPath}
327 } else {
328
329 uvmImagePath, err := uvmfolder.LocateUVMFolder(ctx, coi.Spec.Windows.LayerFolders)
330 if err != nil {
331 return nil, nil, err
332 }
333 v1.HvRuntime = &schema1.HvRuntime{ImagePath: filepath.Join(uvmImagePath, `UtilityVM`)}
334 }
335 } else if coi.isV2Xenon() {
336
337 v2Container.Storage.Path = coi.Spec.Root.Path
338 if coi.HostingSystem.OS() == "windows" {
339 layers, err := layers.GetHCSLayers(ctx, coi.HostingSystem, coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1])
340 if err != nil {
341 return nil, nil, err
342 }
343 v2Container.Storage.Layers = layers
344 }
345 }
346
347 if coi.isV2Argon() || coi.isV1Argon() {
348 for _, layerPath := range coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1] {
349 layerID, err := wclayer.LayerID(ctx, layerPath)
350 if err != nil {
351 return nil, nil, err
352 }
353 v1.Layers = append(v1.Layers, schema1.Layer{ID: layerID.String(), Path: layerPath})
354 v2Container.Storage.Layers = append(v2Container.Storage.Layers, hcsschema.Layer{Id: layerID.String(), Path: layerPath})
355 }
356 }
357
358 mounts, err := createMountsConfig(ctx, coi)
359 if err != nil {
360 return nil, nil, err
361 }
362 v1.MappedDirectories = mounts.mdsv1
363 v2Container.MappedDirectories = mounts.mdsv2
364 if len(mounts.mpsv1) > 0 && osversion.Build() < osversion.RS3 {
365 return nil, nil, fmt.Errorf("named pipe mounts are not supported on this version of Windows")
366 }
367 v1.MappedPipes = mounts.mpsv1
368 v2Container.MappedPipes = mounts.mpsv2
369
370
371 if err := parseAssignedDevices(ctx, coi, v2Container); err != nil {
372 return nil, nil, err
373 }
374
375
376 extensions, err := getDeviceExtensions(coi.Spec.Annotations)
377 if err != nil {
378 return nil, nil, err
379 }
380 v2Container.AdditionalDeviceNamespace = extensions
381
382
383 dumpPath := ""
384 if coi.HostingSystem != nil {
385 dumpPath = coi.HostingSystem.ProcessDumpLocation()
386 }
387
388 if specDumpPath, ok := coi.Spec.Annotations[annotations.ContainerProcessDumpLocation]; ok {
389
390
391
392 dumpPath = specDumpPath
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 registryAdd := []hcsschema.RegistryValue{
409 {
410 Key: &hcsschema.RegistryKey{
411 Hive: "System",
412 Name: "ControlSet001\\Control",
413 },
414 Name: "WaitToKillServiceTimeout",
415 StringValue: strconv.Itoa(math.MaxInt32),
416 Type_: "String",
417 },
418 }
419
420 if dumpPath != "" {
421
422
423
424
425
426 if strings.HasSuffix(dumpPath, createContainerSubdirectoryForProcessDumpSuffix) {
427
428 dumpPath = strings.TrimSuffix(dumpPath, createContainerSubdirectoryForProcessDumpSuffix) + coi.ID
429 } else if strings.HasSuffix(dumpPath, strings.ToUpper(createContainerSubdirectoryForProcessDumpSuffix)) {
430
431 dumpPath = strings.TrimSuffix(dumpPath, strings.ToUpper(createContainerSubdirectoryForProcessDumpSuffix)) + coi.ID
432 }
433 dumpType, err := parseDumpType(coi.Spec.Annotations)
434 if err != nil {
435 return nil, nil, err
436 }
437 dumpCount, err := parseDumpCount(coi.Spec.Annotations)
438 if err != nil {
439 return nil, nil, err
440 }
441
442
443
444 registryAdd = append(registryAdd, []hcsschema.RegistryValue{
445 {
446 Key: &hcsschema.RegistryKey{
447 Hive: "Software",
448 Name: "Microsoft\\Windows\\Windows Error Reporting\\LocalDumps",
449 },
450 Name: "DumpFolder",
451 StringValue: dumpPath,
452 Type_: "String",
453 },
454 {
455 Key: &hcsschema.RegistryKey{
456 Hive: "Software",
457 Name: "Microsoft\\Windows\\Windows Error Reporting\\LocalDumps",
458 },
459 Name: "DumpType",
460 DWordValue: dumpType,
461 Type_: "DWord",
462 },
463 {
464 Key: &hcsschema.RegistryKey{
465 Hive: "Software",
466 Name: "Microsoft\\Windows\\Windows Error Reporting\\LocalDumps",
467 },
468 Name: "DumpCount",
469 DWordValue: dumpCount,
470 Type_: "DWord",
471 },
472 }...)
473 }
474
475 v2Container.RegistryChanges = &hcsschema.RegistryChanges{
476 AddValues: registryAdd,
477 }
478 return v1, v2Container, nil
479 }
480
481
482
483 func parseAssignedDevices(ctx context.Context, coi *createOptionsInternal, v2 *hcsschema.Container) error {
484 if !coi.isV2Argon() && !coi.isV2Xenon() {
485 return nil
486 }
487
488 v2AssignedDevices := []hcsschema.Device{}
489 for _, d := range coi.Spec.Windows.Devices {
490 v2Dev := hcsschema.Device{}
491 switch d.IDType {
492 case uvm.VPCILocationPathIDType:
493 v2Dev.LocationPath = d.ID
494 v2Dev.Type = hcsschema.DeviceInstanceID
495 case uvm.VPCIClassGUIDTypeLegacy:
496 v2Dev.InterfaceClassGuid = d.ID
497 case uvm.VPCIClassGUIDType:
498 v2Dev.InterfaceClassGuid = d.ID
499 default:
500 return fmt.Errorf("specified device %s has unsupported type %s", d.ID, d.IDType)
501 }
502 log.G(ctx).WithField("hcsv2 device", v2Dev).Debug("adding assigned device to container doc")
503 v2AssignedDevices = append(v2AssignedDevices, v2Dev)
504 }
505 v2.AssignedDevices = v2AssignedDevices
506 return nil
507 }
508
509 func parseDumpCount(annots map[string]string) (int32, error) {
510 dmpCountStr := annots[annotations.WCOWProcessDumpCount]
511 if dmpCountStr == "" {
512
513 return 10, nil
514 }
515
516 dumpCount, err := strconv.Atoi(dmpCountStr)
517 if err != nil {
518 return -1, err
519 }
520 if dumpCount > 0 {
521 return int32(dumpCount), nil
522 }
523 return -1, fmt.Errorf("invaid dump count specified: %v", dmpCountStr)
524 }
525
526
527
528
529
530 func parseDumpType(annots map[string]string) (int32, error) {
531 dmpTypeStr := annots[annotations.WCOWProcessDumpType]
532 switch dmpTypeStr {
533 case "":
534
535 return 2, nil
536 case "mini":
537 return 1, nil
538 case "full":
539 return 2, nil
540 default:
541 return -1, errors.New(`unknown dump type specified, valid values are "mini" or "full"`)
542 }
543 }
544
View as plain text