diff --git a/internal/oci/annotations.go b/internal/oci/annotations.go index 64a194c760..7463bc9dfe 100644 --- a/internal/oci/annotations.go +++ b/internal/oci/annotations.go @@ -377,6 +377,19 @@ func ParseAnnotationCommaSeparated(key string, a map[string]string) []string { return results } +// ParseAnnotationsGUID searches `a` for `key`. If `key` is found, tries to parse it as guid.GUID, otherwise +// returns `def`. +func ParseAnnotationsGUID(a map[string]string, key string, def *guid.GUID) (*guid.GUID, error) { + if v, ok := a[key]; ok { + g, err := guid.FromString(v) + if err != nil { + return nil, fmt.Errorf("failed to parse annotation %q with value %q as GUID: %w", key, v, err) + } + return &g, nil + } + return def, nil +} + func logAnnotationValueParseError(ctx context.Context, k, v, et string, err error) { entry := log.G(ctx).WithFields(logrus.Fields{ logfields.OCIAnnotation: k, diff --git a/internal/oci/uvm.go b/internal/oci/uvm.go index f0d9402e1d..b45e6176a0 100644 --- a/internal/oci/uvm.go +++ b/internal/oci/uvm.go @@ -251,7 +251,7 @@ func parseDevices(ctx context.Context, specWindows *specs.Windows) []uvm.VPCIDev } // sets options common to both WCOW and LCOW from annotations. -func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *specs.Spec) { +func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *specs.Spec) error { opts.MemorySizeInMB = ParseAnnotationsMemory(ctx, s, annotations.MemorySizeInMB, opts.MemorySizeInMB) opts.LowMMIOGapInMB = ParseAnnotationsUint64(ctx, s.Annotations, annotations.MemoryLowMMIOGapInMB, opts.LowMMIOGapInMB) opts.HighMMIOBaseInMB = ParseAnnotationsUint64(ctx, s.Annotations, annotations.MemoryHighMMIOBaseInMB, opts.HighMMIOBaseInMB) @@ -282,6 +282,14 @@ func specToUVMCreateOptionsCommon(ctx context.Context, opts *uvm.Options, s *spe opts.NumaMemoryBlocksCounts) maps.Copy(opts.AdditionalHyperVConfig, parseHVSocketServiceTable(ctx, s.Annotations)) + + // parse error yielding annotations + var err error + opts.ResourcePartitionID, err = ParseAnnotationsGUID(s.Annotations, annotations.ResourcePartitionID, opts.ResourcePartitionID) + if err != nil { + return err + } + return nil } // SpecToUVMCreateOpts parses `s` and returns either `*uvm.OptionsLCOW` or @@ -292,7 +300,9 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( } if IsLCOW(s) { lopts := uvm.NewDefaultOptionsLCOW(id, owner) - specToUVMCreateOptionsCommon(ctx, lopts.Options, s) + if err := specToUVMCreateOptionsCommon(ctx, lopts.Options, s); err != nil { + return nil, err + } /* WARNING!!!!!!!!!! @@ -339,7 +349,9 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( return lopts, nil } else if IsWCOW(s) { wopts := uvm.NewDefaultOptionsWCOW(id, owner) - specToUVMCreateOptionsCommon(ctx, wopts.Options, s) + if err := specToUVMCreateOptionsCommon(ctx, wopts.Options, s); err != nil { + return nil, err + } wopts.DisableCompartmentNamespace = ParseAnnotationsBool(ctx, s.Annotations, annotations.DisableCompartmentNamespace, wopts.DisableCompartmentNamespace) wopts.NoDirectMap = ParseAnnotationsBool(ctx, s.Annotations, annotations.VSMBNoDirectMap, wopts.NoDirectMap) diff --git a/internal/tools/uvmboot/main.go b/internal/tools/uvmboot/main.go index 8756162872..0a631f7bc8 100644 --- a/internal/tools/uvmboot/main.go +++ b/internal/tools/uvmboot/main.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/Microsoft/go-winio/pkg/guid" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -24,6 +25,7 @@ const ( measureArgName = "measure" parallelArgName = "parallel" countArgName = "count" + resourcePartitionArgName = "resource-partition" execCommandLineArgName = "exec" uvmConsolePipe = "\\\\.\\pipe\\uvmpipe" @@ -82,6 +84,10 @@ func main() { Usage: "Launch the GCS and perform requested operations via its RPC interface", Destination: &useGCS, }, + cli.StringFlag{ + Name: resourcePartitionArgName, + Usage: "Resource partition GUID to assign UVM to", + }, } app.Commands = []cli.Command{ @@ -125,6 +131,13 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) { if c.GlobalIsSet(enableDeferredCommitArgName) { options.EnableDeferredCommit = c.GlobalBool(enableDeferredCommitArgName) } + if c.GlobalIsSet(resourcePartitionArgName) { + rpID, err := guid.FromString(c.GlobalString(resourcePartitionArgName)) + if err != nil { + logrus.Fatalf("Failed to parse resource partition GUID: %v", err) + } + options.ResourcePartitionID = &rpID + } // Always set the console pipe in uvmboot, it helps with testing/debugging options.ConsolePipe = uvmConsolePipe } diff --git a/internal/uvm/create.go b/internal/uvm/create.go index c736133494..ddd8993e36 100644 --- a/internal/uvm/create.go +++ b/internal/uvm/create.go @@ -84,6 +84,10 @@ type Options struct { // CPUGroupID set the ID of a CPUGroup on the host that the UVM should be added to on start. // Defaults to an empty string which indicates the UVM should not be added to any CPUGroup. CPUGroupID string + + // ResourcePartitionID holds the resource partition guid.GUID the UVM should be assigned to. + ResourcePartitionID *guid.GUID + // NetworkConfigProxy holds the address of the network config proxy service. // This != "" determines whether to start the ComputeAgent TTRPC service // that receives the UVMs set of NICs from this proxy instead of enumerating @@ -171,6 +175,11 @@ func verifyOptions(_ context.Context, options interface{}) error { if opts.EnableColdDiscardHint && osversion.Build() < 18967 { return errors.New("EnableColdDiscardHint is not supported on builds older than 18967") } + if opts.ResourcePartitionID != nil { + if opts.CPUGroupID != "" { + return errors.New("resource partition ID and CPU group ID cannot be set at the same time") + } + } case *OptionsWCOW: if opts.EnableDeferredCommit && !opts.AllowOvercommit { return errors.New("EnableDeferredCommit is not supported on physically backed VMs") @@ -184,6 +193,11 @@ func verifyOptions(_ context.Context, options interface{}) error { if opts.SecurityPolicyEnabled && opts.GuestStateFilePath == "" { return fmt.Errorf("GuestStateFilePath must be provided when enabling security policy") } + if opts.ResourcePartitionID != nil { + if opts.CPUGroupID != "" { + return errors.New("resource partition ID and CPU group ID cannot be set at the same time") + } + } } return nil } diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 3b3c364734..d6d9b4f2d3 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -657,6 +657,11 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs doc.VirtualMachine.ComputeTopology.Memory.SlitType = &firmwareFallbackMeasured } + if opts.ResourcePartitionID != nil { + // TODO (maksiman): assign pod to resource partition and potentially do an OS version check before that + log.G(ctx).WithField("resource-partition-id", opts.ResourcePartitionID.String()).Debug("setting resource partition ID") + } + // Add optional devices that were specified on the UVM spec if len(opts.AssignedDevices) > 0 { if doc.VirtualMachine.Devices.VirtualPci == nil { diff --git a/internal/uvm/create_wcow.go b/internal/uvm/create_wcow.go index b1dbfdd138..9441a72e67 100644 --- a/internal/uvm/create_wcow.go +++ b/internal/uvm/create_wcow.go @@ -231,6 +231,11 @@ func prepareCommonConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWC doc.VirtualMachine.ComputeTopology.Memory.SlitType = &firmwareFallbackMeasured } + if opts.ResourcePartitionID != nil { + // TODO (maksiman): assign pod to resource partition and potentially do an OS version check before that + log.G(ctx).WithField("resource-partition-id", opts.ResourcePartitionID.String()).Debug("setting resource partition ID") + } + maps.Copy(doc.VirtualMachine.Devices.HvSocket.HvSocketConfig.ServiceTable, opts.AdditionalHyperVConfig) // Handle StorageQoS if set diff --git a/pkg/annotations/annotations.go b/pkg/annotations/annotations.go index 31406fa1b9..38653ec233 100644 --- a/pkg/annotations/annotations.go +++ b/pkg/annotations/annotations.go @@ -217,6 +217,7 @@ const ( // uVM CPU annotations. const ( // CPUGroupID specifies the cpugroup ID that a UVM should be assigned to, if any. + // Passing this annotation together with ResourcePartitionID will result in an error. CPUGroupID = "io.microsoft.virtualmachine.cpugroup.id" // ProcessorCount overrides the hypervisor isolated vCPU count set @@ -312,6 +313,11 @@ const ( // number of memory blocks at slice index 1, etc. // This should be used for explicit vNUMA topology. NumaCountOfMemoryBlocks = "io.microsoft.virtualmachine.computetopology.numa.count-of-memory-blocks" + + // ResourcePartitionID is a GUID string representing a resource partition ID the UVM should be associated with. + // Resource partition will have its own CPU group, as a result this annotation cannot be used together with + // CPUGroupID and will yield an error. + ResourcePartitionID = "io.microsoft.virtualmachine.resource-partition-id" ) // uVM storage (Quality of Service) annotations.