Skip to content

Commit b6f40e1

Browse files
committed
refactor uvm common code into vmutils package
Presently, all the utility methods for uvm building were coupled within the `internal/uvm` package. In order to enhance reusability across shims, we want to move these into a common package. This commit accomplishes that for methods in the CreateUVM path. Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
1 parent 2394aa1 commit b6f40e1

16 files changed

Lines changed: 1282 additions & 98 deletions

File tree

cmd/containerd-shim-runhcs-v1/service_internal.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (s *service) createInternal(ctx context.Context, req *task.CreateTaskReques
107107

108108
spec = oci.UpdateSpecFromOptions(spec, shimOpts)
109109
// expand annotations after defaults have been loaded in from options
110-
err = oci.ProcessAnnotations(ctx, &spec)
110+
err = oci.ProcessAnnotations(ctx, spec.Annotations)
111111
// since annotation expansion is used to toggle security features
112112
// raise it rather than suppress and move on
113113
if err != nil {

internal/oci/annotations.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var ErrAnnotationExpansionConflict = errors.New("annotation expansion conflict")
2424
var ErrGenericAnnotationConflict = errors.New("specified annotations conflict")
2525

2626
// ProcessAnnotations expands annotations into their corresponding annotation groups.
27-
func ProcessAnnotations(ctx context.Context, s *specs.Spec) error {
27+
func ProcessAnnotations(ctx context.Context, specAnnotations map[string]string) error {
2828
// Named `Process` and not `Expand` since this function may be expanded (pun intended) to
2929
// deal with other annotation issues and validation.
3030

@@ -36,11 +36,11 @@ func ProcessAnnotations(ctx context.Context, s *specs.Spec) error {
3636
var errs []error
3737
for key, exps := range annotations.AnnotationExpansionMap() {
3838
// check if annotation is present
39-
if val, ok := s.Annotations[key]; ok {
39+
if val, ok := specAnnotations[key]; ok {
4040
// ideally, some normalization would occur here (ie, "True" -> "true")
4141
// but strings may be case-sensitive
4242
for _, k := range exps {
43-
if v, ok := s.Annotations[k]; ok && val != v {
43+
if v, ok := specAnnotations[k]; ok && val != v {
4444
err := fmt.Errorf("%w: %q = %q and %q = %q", ErrAnnotationExpansionConflict, key, val, k, v)
4545
errs = append(errs, err)
4646
log.G(ctx).WithFields(logrus.Fields{
@@ -51,26 +51,26 @@ func ProcessAnnotations(ctx context.Context, s *specs.Spec) error {
5151
}).WithError(err).Warning("annotation expansion would overwrite conflicting value")
5252
continue
5353
}
54-
s.Annotations[k] = val
54+
specAnnotations[k] = val
5555
}
5656
}
5757
}
5858

5959
// validate host process containers annotations are not conflicting
60-
disableHPC := ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableHostProcessContainer, false)
61-
enableHPC := ParseAnnotationsBool(ctx, s.Annotations, annotations.HostProcessContainer, false)
60+
disableHPC := ParseAnnotationsBool(ctx, specAnnotations, annotations.DisableHostProcessContainer, false)
61+
enableHPC := ParseAnnotationsBool(ctx, specAnnotations, annotations.HostProcessContainer, false)
6262
if disableHPC && enableHPC {
6363
err := fmt.Errorf("%w: host process container annotations %q = %q and %q = %q",
6464
ErrGenericAnnotationConflict,
65-
annotations.DisableHostProcessContainer, s.Annotations[annotations.DisableHostProcessContainer],
66-
annotations.HostProcessContainer, s.Annotations[annotations.HostProcessContainer])
65+
annotations.DisableHostProcessContainer, specAnnotations[annotations.DisableHostProcessContainer],
66+
annotations.HostProcessContainer, specAnnotations[annotations.HostProcessContainer])
6767
errs = append(errs, err)
6868

6969
log.G(ctx).WithFields(logrus.Fields{
7070
logfields.OCIAnnotation: annotations.DisableHostProcessContainer,
71-
logfields.Value: s.Annotations[annotations.DisableHostProcessContainer],
71+
logfields.Value: specAnnotations[annotations.DisableHostProcessContainer],
7272
logfields.OCIAnnotation + "-conflict": annotations.HostProcessContainer,
73-
logfields.Value + "-conflict": s.Annotations[annotations.HostProcessContainer],
73+
logfields.Value + "-conflict": specAnnotations[annotations.HostProcessContainer],
7474
}).WithError(err).Warning("Host process container and disable host process container cannot both be true")
7575
}
7676

@@ -204,10 +204,10 @@ func parseAdditionalRegistryValues(ctx context.Context, a map[string]string) []h
204204
return slices.Clip(rvs)
205205
}
206206

207-
// parseHVSocketServiceTable extracts any additional Hyper-V socket service configurations from annotations.
207+
// ParseHVSocketServiceTable extracts any additional Hyper-V socket service configurations from annotations.
208208
//
209209
// Like the [parseAnnotation*] functions, this logs errors but does not return them.
210-
func parseHVSocketServiceTable(ctx context.Context, a map[string]string) map[string]hcsschema.HvSocketServiceConfig {
210+
func ParseHVSocketServiceTable(ctx context.Context, a map[string]string) map[string]hcsschema.HvSocketServiceConfig {
211211
sc := make(map[string]hcsschema.HvSocketServiceConfig)
212212
// TODO(go1.23) use range over functions to implement a functional `filter | map $ a`
213213
for k, v := range a {

internal/oci/annotations_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,10 @@ func TestProccessAnnotations_HostProcessContainer(t *testing.T) {
117117
for _, tt := range testAnnotations {
118118
t.Run(tt.name, func(t *testing.T) {
119119
spec := specs.Spec{
120-
Windows: &specs.Windows{},
121120
Annotations: tt.an,
122121
}
123122

124-
err := ProcessAnnotations(ctx, &spec)
123+
err := ProcessAnnotations(ctx, spec.Annotations)
125124
if err != nil && len(tt.errs) == 0 {
126125
t.Fatalf("ProcessAnnotations should have succeeded, instead got %v", err)
127126
}
@@ -181,7 +180,7 @@ func TestProccessAnnotations_Expansion(t *testing.T) {
181180
annotations.DisableUnsafeOperations: v,
182181
}
183182

184-
err := ProcessAnnotations(ctx, &tt.spec)
183+
err := ProcessAnnotations(ctx, tt.spec.Annotations)
185184
if err != nil {
186185
subtest.Fatalf("could not update spec from options: %v", err)
187186
}
@@ -206,7 +205,7 @@ func TestProccessAnnotations_Expansion(t *testing.T) {
206205
annotations.DisableUnsafeOperations,
207206
annotations.DisableWritableFileShares)
208207

209-
err := ProcessAnnotations(ctx, &tt.spec)
208+
err := ProcessAnnotations(ctx, tt.spec.Annotations)
210209
if !errors.Is(err, ErrAnnotationExpansionConflict) {
211210
t.Fatalf("UpdateSpecFromOptions should have failed with %q, actual was %v", errExp, err)
212211
}
@@ -460,7 +459,7 @@ func TestParseHVSocketServiceTable(t *testing.T) {
460459
maps.Copy(annots, tt.give)
461460
t.Logf("annotations:\n%v", annots)
462461

463-
rvs := parseHVSocketServiceTable(ctx, annots)
462+
rvs := ParseHVSocketServiceTable(ctx, annots)
464463
t.Logf("got %v", rvs)
465464
want := tt.want
466465
if want == nil {

internal/oci/uvm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *spe
333333
opts.NumaMemoryBlocksCounts = ParseAnnotationCommaSeparatedUint64(ctx, s.Annotations, annotations.NumaCountOfMemoryBlocks,
334334
opts.NumaMemoryBlocksCounts)
335335

336-
maps.Copy(opts.AdditionalHyperVConfig, parseHVSocketServiceTable(ctx, s.Annotations))
336+
maps.Copy(opts.AdditionalHyperVConfig, ParseHVSocketServiceTable(ctx, s.Annotations))
337337

338338
// parse error yielding annotations
339339
var err error

internal/uvm/create.go

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99
"os"
1010
"path/filepath"
11-
"runtime"
1211

1312
"github.com/Microsoft/go-winio/pkg/guid"
1413
"github.com/sirupsen/logrus"
@@ -22,6 +21,7 @@ import (
2221
"github.com/Microsoft/hcsshim/internal/logfields"
2322
"github.com/Microsoft/hcsshim/internal/oc"
2423
"github.com/Microsoft/hcsshim/internal/schemaversion"
24+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
2525
"github.com/Microsoft/hcsshim/osversion"
2626
)
2727

@@ -226,7 +226,7 @@ func newDefaultOptions(id, owner string) *Options {
226226
MemorySizeInMB: 1024,
227227
AllowOvercommit: true,
228228
EnableDeferredCommit: false,
229-
ProcessorCount: defaultProcessorCount(),
229+
ProcessorCount: vmutils.DefaultProcessorCountForUVM(),
230230
FullyPhysicallyBacked: false,
231231
NoWritableFileShares: false,
232232
SCSIControllerCount: 1,
@@ -395,36 +395,6 @@ func (uvm *UtilityVM) ExitError() error {
395395
return uvm.hcsSystem.ExitError()
396396
}
397397

398-
func defaultProcessorCount() int32 {
399-
if runtime.NumCPU() == 1 {
400-
return 1
401-
}
402-
return 2
403-
}
404-
405-
// normalizeProcessorCount sets `uvm.processorCount` to `Min(requested,
406-
// logical CPU count)`.
407-
func (uvm *UtilityVM) normalizeProcessorCount(ctx context.Context, requested int32, processorTopology *hcsschema.ProcessorTopology) int32 {
408-
// Use host processor information retrieved from HCS instead of runtime.NumCPU,
409-
// GetMaximumProcessorCount or other OS level calls for two reasons.
410-
// 1. Go uses GetProcessAffinityMask and falls back to GetSystemInfo both of
411-
// which will not return LPs in another processor group.
412-
// 2. GetMaximumProcessorCount will return all processors on the system
413-
// but in configurations where the host partition doesn't see the full LP count
414-
// i.e "Minroot" scenarios this won't be sufficient.
415-
// (https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/manage/manage-hyper-v-minroot-2016)
416-
hostCount := int32(processorTopology.LogicalProcessorCount)
417-
if requested > hostCount {
418-
log.G(ctx).WithFields(logrus.Fields{
419-
logfields.UVMID: uvm.id,
420-
"requested": requested,
421-
"assigned": hostCount,
422-
}).Warn("Changing user requested CPUCount to current number of processors")
423-
return hostCount
424-
}
425-
return requested
426-
}
427-
428398
// ProcessorCount returns the number of processors actually assigned to the UVM.
429399
func (uvm *UtilityVM) ProcessorCount() int32 {
430400
return uvm.processorCount
@@ -442,18 +412,6 @@ func (uvm *UtilityVM) ProcessDumpLocation() string {
442412
return uvm.processDumpLocation
443413
}
444414

445-
func (uvm *UtilityVM) normalizeMemorySize(ctx context.Context, requested uint64) uint64 {
446-
actual := (requested + 1) &^ 1 // align up to an even number
447-
if requested != actual {
448-
log.G(ctx).WithFields(logrus.Fields{
449-
logfields.UVMID: uvm.id,
450-
"requested": requested,
451-
"assigned": actual,
452-
}).Warn("Changing user requested MemorySizeInMB to align to 2MB")
453-
}
454-
return actual
455-
}
456-
457415
// DevicesPhysicallyBacked describes if additional devices added to the UVM
458416
// should be physically backed.
459417
func (uvm *UtilityVM) DevicesPhysicallyBacked() bool {

internal/uvm/create_lcow.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/Microsoft/hcsshim/internal/schemaversion"
3131
"github.com/Microsoft/hcsshim/internal/security"
3232
"github.com/Microsoft/hcsshim/internal/uvm/scsi"
33+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
3334
"github.com/Microsoft/hcsshim/osversion"
3435
)
3536

@@ -130,18 +131,6 @@ type OptionsLCOW struct {
130131
WritableOverlayDirs bool // Whether init should create writable overlay mounts for /var and /etc
131132
}
132133

133-
// defaultLCOWOSBootFilesPath returns the default path used to locate the LCOW
134-
// OS kernel and root FS files. This default is the subdirectory
135-
// `LinuxBootFiles` in the directory of the executable that started the current
136-
// process; or, if it does not exist, `%ProgramFiles%\Linux Containers`.
137-
func defaultLCOWOSBootFilesPath() string {
138-
localDirPath := filepath.Join(filepath.Dir(os.Args[0]), "LinuxBootFiles")
139-
if _, err := os.Stat(localDirPath); err == nil {
140-
return localDirPath
141-
}
142-
return filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers")
143-
}
144-
145134
// NewDefaultOptionsLCOW creates the default options for a bootable version of
146135
// LCOW.
147136
//
@@ -179,7 +168,7 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW {
179168
},
180169
}
181170

182-
opts.UpdateBootFilesPath(context.TODO(), defaultLCOWOSBootFilesPath())
171+
opts.UpdateBootFilesPath(context.TODO(), vmutils.DefaultLCOWOSBootFilesPath())
183172

184173
return opts
185174
}
@@ -243,7 +232,7 @@ func fetchProcessor(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (*hc
243232

244233
// To maintain compatibility with Docker we need to automatically downgrade
245234
// a user CPU count if the setting is not possible.
246-
uvm.processorCount = uvm.normalizeProcessorCount(ctx, opts.ProcessorCount, processorTopology)
235+
uvm.processorCount = vmutils.NormalizeProcessorCount(ctx, uvm.id, opts.ProcessorCount, processorTopology)
247236

248237
processor := &hcsschema.VirtualMachineProcessor{
249238
Count: uint32(uvm.processorCount),
@@ -394,7 +383,7 @@ func makeLCOWVMGSDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_
394383
}
395384

396385
// Align the requested memory size.
397-
memorySizeInMB := uvm.normalizeMemorySize(ctx, opts.MemorySizeInMB)
386+
memorySizeInMB := vmutils.NormalizeMemorySize(ctx, uvm.id, opts.MemorySizeInMB)
398387

399388
doc := &hcsschema.ComputeSystem{
400389
Owner: uvm.owner,
@@ -596,19 +585,26 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs
596585
return nil, err
597586
}
598587

599-
numa, numaProcessors, err := prepareVNumaTopology(ctx, opts.Options)
588+
numa, numaProcessors, err := vmutils.PrepareVNumaTopology(ctx, &vmutils.NumaConfig{
589+
MaxProcessorsPerNumaNode: opts.MaxProcessorsPerNumaNode,
590+
MaxMemorySizePerNumaNode: opts.MaxMemorySizePerNumaNode,
591+
PreferredPhysicalNumaNodes: opts.PreferredPhysicalNumaNodes,
592+
NumaMappedPhysicalNodes: opts.NumaMappedPhysicalNodes,
593+
NumaProcessorCounts: opts.NumaProcessorCounts,
594+
NumaMemoryBlocksCounts: opts.NumaMemoryBlocksCounts,
595+
})
600596
if err != nil {
601597
return nil, err
602598
}
603599

604600
// Align the requested memory size.
605-
memorySizeInMB := uvm.normalizeMemorySize(ctx, opts.MemorySizeInMB)
601+
memorySizeInMB := vmutils.NormalizeMemorySize(ctx, uvm.id, opts.MemorySizeInMB)
606602

607603
if numa != nil {
608604
if opts.AllowOvercommit {
609605
return nil, fmt.Errorf("vNUMA supports only Physical memory backing type")
610606
}
611-
if err := validateNumaForVM(numa, processor.Count, memorySizeInMB); err != nil {
607+
if err := vmutils.ValidateNumaForVM(numa, processor.Count, memorySizeInMB); err != nil {
612608
return nil, fmt.Errorf("failed to validate vNUMA settings: %w", err)
613609
}
614610
}

internal/uvm/create_wcow.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/Microsoft/hcsshim/internal/schemaversion"
2828
"github.com/Microsoft/hcsshim/internal/security"
2929
"github.com/Microsoft/hcsshim/internal/uvm/scsi"
30+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
3031
"github.com/Microsoft/hcsshim/internal/wclayer"
3132
"github.com/Microsoft/hcsshim/osversion"
3233
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
@@ -146,10 +147,10 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC
146147

147148
// To maintain compatibility with Docker we need to automatically downgrade
148149
// a user CPU count if the setting is not possible.
149-
uvm.processorCount = uvm.normalizeProcessorCount(ctx, opts.ProcessorCount, processorTopology)
150+
uvm.processorCount = vmutils.NormalizeProcessorCount(ctx, uvm.id, opts.ProcessorCount, processorTopology)
150151

151152
// Align the requested memory size.
152-
memorySizeInMB := uvm.normalizeMemorySize(ctx, opts.MemorySizeInMB)
153+
memorySizeInMB := vmutils.NormalizeMemorySize(ctx, uvm.id, opts.MemorySizeInMB)
153154

154155
var registryChanges hcsschema.RegistryChanges
155156
// We're getting asked to setup local dump collection for WCOW. We need to:
@@ -209,7 +210,14 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC
209210
Weight: uint64(opts.ProcessorWeight),
210211
}
211212

212-
numa, numaProcessors, err := prepareVNumaTopology(ctx, opts.Options)
213+
numa, numaProcessors, err := vmutils.PrepareVNumaTopology(ctx, &vmutils.NumaConfig{
214+
MaxProcessorsPerNumaNode: opts.MaxProcessorsPerNumaNode,
215+
MaxMemorySizePerNumaNode: opts.MaxMemorySizePerNumaNode,
216+
PreferredPhysicalNumaNodes: opts.PreferredPhysicalNumaNodes,
217+
NumaMappedPhysicalNodes: opts.NumaMappedPhysicalNodes,
218+
NumaProcessorCounts: opts.NumaProcessorCounts,
219+
NumaMemoryBlocksCounts: opts.NumaMemoryBlocksCounts,
220+
})
213221
if err != nil {
214222
return nil, err
215223
}
@@ -218,7 +226,7 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC
218226
if opts.AllowOvercommit {
219227
return nil, fmt.Errorf("vNUMA supports only Physical memory backing type")
220228
}
221-
if err := validateNumaForVM(numa, processor.Count, memorySizeInMB); err != nil {
229+
if err := vmutils.ValidateNumaForVM(numa, processor.Count, memorySizeInMB); err != nil {
222230
return nil, fmt.Errorf("failed to validate vNUMA settings: %w", err)
223231
}
224232
}

internal/uvm/memory_update.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
1010
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
1111
"github.com/Microsoft/hcsshim/internal/memory"
12+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
1213
)
1314

1415
const bytesPerPage = 4096
@@ -18,7 +19,7 @@ const bytesPerPage = 4096
1819
// pages to numa nodes evenly
1920
func (uvm *UtilityVM) UpdateMemory(ctx context.Context, sizeInBytes uint64) error {
2021
requestedSizeInMB := sizeInBytes / memory.MiB
21-
actual := uvm.normalizeMemorySize(ctx, requestedSizeInMB)
22+
actual := vmutils.NormalizeMemorySize(ctx, uvm.id, requestedSizeInMB)
2223
req := &hcsschema.ModifySettingRequest{
2324
ResourcePath: resourcepaths.MemoryResourcePath,
2425
Settings: actual,

internal/uvm/start.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
3131
"github.com/Microsoft/hcsshim/internal/timeout"
3232
"github.com/Microsoft/hcsshim/internal/uvm/scsi"
33+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
3334
)
3435

3536
// entropyBytes is the number of bytes of random data to send to a Linux UVM
@@ -379,7 +380,7 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) {
379380
policy = uvm.createOpts.(*OptionsLCOW).SecurityPolicy
380381
enforcer = uvm.createOpts.(*OptionsLCOW).SecurityPolicyEnforcer
381382
referenceInfoFilePath = uvm.createOpts.(*OptionsLCOW).UVMReferenceInfoFile
382-
referenceInfoFileRoot = defaultLCOWOSBootFilesPath()
383+
referenceInfoFileRoot = vmutils.DefaultLCOWOSBootFilesPath()
383384
} else if uvm.OS() == "windows" {
384385
policy = uvm.createOpts.(*OptionsWCOW).SecurityPolicy
385386
enforcer = uvm.createOpts.(*OptionsWCOW).SecurityPolicyEnforcer

internal/vm/vmutils/doc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build windows
2+
3+
// Package vmutils provides shared utility functions for working with Utility VMs.
4+
//
5+
// This package contains stateless utility functions that can be used by both the
6+
// legacy UVM management code (internal/uvm) and the new controller-based architecture
7+
// (internal/controller). Functions in this package are designed to be decoupled from
8+
// specific UVM implementations.
9+
//
10+
// This allows different shims (containerd-shim-runhcs-v1, containerd-shim-lcow-v1)
11+
// to share common logic while maintaining their own orchestration patterns.
12+
package vmutils

0 commit comments

Comments
 (0)