diff --git a/internal/oci/uvm.go b/internal/oci/uvm.go index 79a8893459..c506b4c97b 100644 --- a/internal/oci/uvm.go +++ b/internal/oci/uvm.go @@ -140,8 +140,14 @@ func parseAnnotationsPreferredRootFSType(ctx context.Context, a map[string]strin return def } -// handleAnnotationKernelDirectBoot handles parsing annotationKernelDirectBoot and setting -// implied annotations from the result. +// handleAnnotationBootFilesPath handles parsing annotations.BootFilesRootPath and setting +// implied options from the result. +func handleAnnotationBootFilesPath(ctx context.Context, a map[string]string, lopts *uvm.OptionsLCOW) { + lopts.UpdateBootFilesPath(ctx, parseAnnotationsString(a, annotations.BootFilesRootPath, lopts.BootFilesPath)) +} + +// handleAnnotationKernelDirectBoot handles parsing annotations.KernelDirectBoot and setting +// implied options from the result. func handleAnnotationKernelDirectBoot(ctx context.Context, a map[string]string, lopts *uvm.OptionsLCOW) { lopts.KernelDirect = ParseAnnotationsBool(ctx, a, annotations.KernelDirectBoot, lopts.KernelDirect) if !lopts.KernelDirect { @@ -149,8 +155,8 @@ func handleAnnotationKernelDirectBoot(ctx context.Context, a map[string]string, } } -// handleAnnotationPreferredRootFSType handles parsing annotationPreferredRootFSType and setting -// implied annotations from the result +// handleAnnotationPreferredRootFSType handles parsing annotations.PreferredRootFSType and setting +// implied options from the result func handleAnnotationPreferredRootFSType(ctx context.Context, a map[string]string, lopts *uvm.OptionsLCOW) { lopts.PreferredRootFSType = parseAnnotationsPreferredRootFSType(ctx, a, annotations.PreferredRootFSType, lopts.PreferredRootFSType) switch lopts.PreferredRootFSType { @@ -161,8 +167,8 @@ func handleAnnotationPreferredRootFSType(ctx context.Context, a map[string]strin } } -// handleAnnotationFullyPhysicallyBacked handles parsing annotationFullyPhysicallyBacked and setting -// implied annotations from the result. For both LCOW and WCOW options. +// handleAnnotationFullyPhysicallyBacked handles parsing annotations.FullyPhysicallyBacked and setting +// implied options from the result. For both LCOW and WCOW options. func handleAnnotationFullyPhysicallyBacked(ctx context.Context, a map[string]string, opts interface{}) { switch options := opts.(type) { case *uvm.OptionsLCOW: @@ -244,7 +250,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( WARNING!!!!!!!!!! When adding an option here which must match some security policy by default, make sure that the correct default (ie matches - a default security policy) is applied in handleSecurityPolicy. Inadvertantly adding an "option" which defaults to false but MUST be + a default security policy) is applied in handleSecurityPolicy. Inadvertently adding an "option" which defaults to false but MUST be true for a default security policy to work will force the annotation to have be set by the team that owns the box. That will be practically difficult and we might not find out until a little late in the process. */ @@ -254,7 +260,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( lopts.VPMemSizeBytes = parseAnnotationsUint64(ctx, s.Annotations, annotations.VPMemSize, lopts.VPMemSizeBytes) lopts.VPMemNoMultiMapping = ParseAnnotationsBool(ctx, s.Annotations, annotations.VPMemNoMultiMapping, lopts.VPMemNoMultiMapping) lopts.VPCIEnabled = ParseAnnotationsBool(ctx, s.Annotations, annotations.VPCIEnabled, lopts.VPCIEnabled) - lopts.BootFilesPath = parseAnnotationsString(s.Annotations, annotations.BootFilesRootPath, lopts.BootFilesPath) + handleAnnotationBootFilesPath(ctx, s.Annotations, lopts) lopts.EnableScratchEncryption = ParseAnnotationsBool(ctx, s.Annotations, annotations.EncryptedScratchDisk, lopts.EnableScratchEncryption) lopts.SecurityPolicy = parseAnnotationsString(s.Annotations, annotations.SecurityPolicy, lopts.SecurityPolicy) lopts.SecurityPolicyEnforcer = parseAnnotationsString(s.Annotations, annotations.SecurityPolicyEnforcer, lopts.SecurityPolicyEnforcer) @@ -274,6 +280,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) ( // override the default GuestState filename if specified lopts.GuestStateFile = parseAnnotationsString(s.Annotations, annotations.GuestStateFile, lopts.GuestStateFile) + return lopts, nil } else if IsWCOW(s) { wopts := uvm.NewDefaultOptionsWCOW(id, owner) diff --git a/internal/tools/uvmboot/lcow.go b/internal/tools/uvmboot/lcow.go index 6725c56648..181fdc00ec 100644 --- a/internal/tools/uvmboot/lcow.go +++ b/internal/tools/uvmboot/lcow.go @@ -161,13 +161,13 @@ func init() { `.\uvmboot.exe -gcs lcow -boot-files-path "C:\ContainerPlat\LinuxBootFiles" -root-fs-type vhd -t -exec "/bin/bash"` } -func createLCOWOptions(_ context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) { +func createLCOWOptions(ctx context.Context, c *cli.Context, id string) (*uvm.OptionsLCOW, error) { options := uvm.NewDefaultOptionsLCOW(id, "") setGlobalOptions(c, options.Options) // boot if c.IsSet(bootFilesPathArgName) { - options.BootFilesPath = c.String(bootFilesPathArgName) + options.UpdateBootFilesPath(ctx, bootFilesPathArgName) } // kernel diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 9a80cafeeb..9da583bc5f 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -100,7 +100,10 @@ type OptionsLCOW struct { *Options *ConfidentialOptions - BootFilesPath string // Folder in which kernel and root file system reside. Defaults to \Program Files\Linux Containers + // Folder in which kernel and root file system reside. Defaults to \Program Files\Linux Containers. + // + // It is preferred to use [UpdateBootFilesPath] to change this value and update associated fields. + BootFilesPath string KernelFile string // Filename under `BootFilesPath` for the kernel. Defaults to `kernel` KernelDirect bool // Skip UEFI and boot directly to `kernel` RootFSFile string // Filename under `BootFilesPath` for the UVMs root file system. Defaults to `InitrdFile` @@ -146,7 +149,6 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW { kernelDirectSupported := osversion.Build() >= 18286 opts := &OptionsLCOW{ Options: newDefaultOptions(id, owner), - BootFilesPath: defaultLCOWOSBootFilesPath(), KernelFile: KernelFile, KernelDirect: kernelDirectSupported, RootFSFile: InitrdFile, @@ -172,22 +174,59 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW { }, } + opts.UpdateBootFilesPath(context.TODO(), defaultLCOWOSBootFilesPath()) + + return opts +} + +// UpdateBootFilesPath updates the LCOW BootFilesPath field and associated settings. +// Specifically, if [VhdFile] is found in path, RootFS is updated, and, if KernelDirect is set, +// KernelFile is also updated if [UncompressedKernelFile] is found in path. +// +// This is a nop if the current BootFilesPath is equal to path (case-insensitive). +func (opts *OptionsLCOW) UpdateBootFilesPath(ctx context.Context, path string) { + if p, err := filepath.Abs(path); err == nil { + path = p + } else { + // if its a filesystem issue, we'll error out elsewhere when we try to access the boot files + // otherwise, it might be transient, or a Go issue, so log and move on + log.G(ctx).WithFields(logrus.Fields{ + logfields.Path: p, + logrus.ErrorKey: err, + }).Warning("could not make boot files path absolute") + } + + if strings.EqualFold(opts.BootFilesPath, path) { // Windows is case-insensitive, so compare paths that way too + return + } + + opts.BootFilesPath = path + if _, err := os.Stat(filepath.Join(opts.BootFilesPath, VhdFile)); err == nil { // We have a rootfs.vhd in the boot files path. Use it over an initrd.img opts.RootFSFile = VhdFile opts.PreferredRootFSType = PreferredRootFSTypeVHD + + log.G(ctx).WithFields(logrus.Fields{ + logfields.UVMID: opts.ID, + VhdFile: filepath.Join(opts.BootFilesPath, VhdFile), + }).Debug("updated LCOW root filesystem to " + VhdFile) } - if kernelDirectSupported { + if opts.KernelDirect { // KernelDirect supports uncompressed kernel if the kernel is present. // Default to uncompressed if on box. NOTE: If `kernel` is already // uncompressed and simply named 'kernel' it will still be used // uncompressed automatically. if _, err := os.Stat(filepath.Join(opts.BootFilesPath, UncompressedKernelFile)); err == nil { opts.KernelFile = UncompressedKernelFile + + log.G(ctx).WithFields(logrus.Fields{ + logfields.UVMID: opts.ID, + UncompressedKernelFile: filepath.Join(opts.BootFilesPath, UncompressedKernelFile), + }).Debug("updated LCOW kernel file to " + UncompressedKernelFile) } } - return opts } // Get an acceptable number of processors given option and actual constraints. @@ -759,7 +798,7 @@ func CreateLCOW(ctx context.Context, opts *OptionsLCOW) (_ *UtilityVM, err error } span.AddAttributes(trace.StringAttribute(logfields.UVMID, opts.ID)) - log.G(ctx).WithField("options", fmt.Sprintf("%+v", opts)).Debug("uvm::CreateLCOW options") + log.G(ctx).WithField("options", log.Format(ctx, opts)).Debug("uvm::CreateLCOW options") // We dont serialize OutputHandler so if it is missing we need to put it back to the default. if opts.OutputHandler == nil { diff --git a/internal/uvm/create_test.go b/internal/uvm/create_test.go index 9685806a14..3cd4ebd790 100644 --- a/internal/uvm/create_test.go +++ b/internal/uvm/create_test.go @@ -11,10 +11,11 @@ import ( // Unit tests for negative testing of input to uvm.Create() func TestCreateBadBootFilesPath(t *testing.T) { + ctx := context.Background() opts := NewDefaultOptionsLCOW(t.Name(), "") - opts.BootFilesPath = `c:\does\not\exist\I\hope` + opts.UpdateBootFilesPath(ctx, `c:\does\not\exist\I\hope`) - _, err := CreateLCOW(context.Background(), opts) + _, err := CreateLCOW(ctx, opts) if err == nil || err.Error() != `kernel: 'c:\does\not\exist\I\hope\kernel' not found` { t.Fatal(err) } diff --git a/test/functional/main_test.go b/test/functional/main_test.go index 30c69a4492..e27544824b 100644 --- a/test/functional/main_test.go +++ b/test/functional/main_test.go @@ -186,9 +186,9 @@ func requireFeatures(tb testing.TB, features ...string) { func defaultLCOWOptions(tb testing.TB) *uvm.OptionsLCOW { tb.Helper() - opts := testuvm.DefaultLCOWOptions(tb, util.CleanName(tb.Name()), hcsOwner) + opts := testuvm.DefaultLCOWOptions(context.TODO(), tb, util.CleanName(tb.Name()), hcsOwner) if p := *flagLinuxBootFilesPath; p != "" { - opts.BootFilesPath = p + opts.UpdateBootFilesPath(context.TODO(), p) } return opts } diff --git a/test/functional/uvm_virtualdevice_test.go b/test/functional/uvm_virtualdevice_test.go index 4b043fdc36..089a199bb4 100644 --- a/test/functional/uvm_virtualdevice_test.go +++ b/test/functional/uvm_virtualdevice_test.go @@ -56,7 +56,7 @@ func TestVirtualDevice(t *testing.T) { opts.KernelFile = uvm.KernelFile opts.RootFSFile = uvm.InitrdFile opts.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd - opts.BootFilesPath = lcowGPUBootFilesPath + opts.UpdateBootFilesPath(ctx, lcowGPUBootFilesPath) // create test uvm and ensure we can assign and remove the device vm := tuvm.CreateAndStartLCOWFromOpts(ctx, t, opts) diff --git a/test/pkg/uvm/lcow.go b/test/pkg/uvm/lcow.go index a42d432705..16c0bfb93a 100644 --- a/test/pkg/uvm/lcow.go +++ b/test/pkg/uvm/lcow.go @@ -41,11 +41,11 @@ func init() { // path. // // See [uvm.NewDefaultOptionsLCOW] for more information. -func DefaultLCOWOptions(tb testing.TB, id, owner string) *uvm.OptionsLCOW { +func DefaultLCOWOptions(ctx context.Context, tb testing.TB, id, owner string) *uvm.OptionsLCOW { tb.Helper() opts := uvm.NewDefaultOptionsLCOW(id, owner) if lcowOSBootFiles != "" { - opts.BootFilesPath = lcowOSBootFiles + opts.UpdateBootFilesPath(ctx, lcowOSBootFiles) } return opts } @@ -53,7 +53,7 @@ func DefaultLCOWOptions(tb testing.TB, id, owner string) *uvm.OptionsLCOW { // CreateAndStartLCOW with all default options. func CreateAndStartLCOW(ctx context.Context, tb testing.TB, id string) *uvm.UtilityVM { tb.Helper() - return CreateAndStartLCOWFromOpts(ctx, tb, DefaultLCOWOptions(tb, id, "")) + return CreateAndStartLCOWFromOpts(ctx, tb, DefaultLCOWOptions(ctx, tb, id, "")) } // CreateAndStartLCOWFromOpts creates an LCOW utility VM with the specified options.