...

Source file src/github.com/Microsoft/hcsshim/internal/uvm/create_wcow.go

Documentation: github.com/Microsoft/hcsshim/internal/uvm

     1  //go:build windows
     2  
     3  package uvm
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/Microsoft/go-winio"
    12  	"github.com/Microsoft/go-winio/pkg/guid"
    13  	"github.com/pkg/errors"
    14  	"go.opencensus.io/trace"
    15  
    16  	"github.com/Microsoft/hcsshim/internal/gcs"
    17  	hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
    18  	"github.com/Microsoft/hcsshim/internal/log"
    19  	"github.com/Microsoft/hcsshim/internal/logfields"
    20  	"github.com/Microsoft/hcsshim/internal/oc"
    21  	"github.com/Microsoft/hcsshim/internal/processorinfo"
    22  	"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
    23  	"github.com/Microsoft/hcsshim/internal/schemaversion"
    24  	"github.com/Microsoft/hcsshim/internal/security"
    25  	"github.com/Microsoft/hcsshim/internal/uvmfolder"
    26  	"github.com/Microsoft/hcsshim/internal/wclayer"
    27  	"github.com/Microsoft/hcsshim/internal/wcow"
    28  	"github.com/Microsoft/hcsshim/osversion"
    29  )
    30  
    31  // OptionsWCOW are the set of options passed to CreateWCOW() to create a utility vm.
    32  type OptionsWCOW struct {
    33  	*Options
    34  
    35  	LayerFolders []string // Set of folders for base layers and scratch. Ordered from top most read-only through base read-only layer, followed by scratch
    36  
    37  	// NoDirectMap specifies that no direct mapping should be used for any VSMBs added to the UVM
    38  	NoDirectMap bool
    39  
    40  	// NoInheritHostTimezone specifies whether to not inherit the hosts timezone for the UVM. UTC will be set as the default for the VM instead.
    41  	NoInheritHostTimezone bool
    42  }
    43  
    44  // NewDefaultOptionsWCOW creates the default options for a bootable version of
    45  // WCOW. The caller `MUST` set the `LayerFolders` path on the returned value.
    46  //
    47  // `id` the ID of the compute system. If not passed will generate a new GUID.
    48  //
    49  // `owner` the owner of the compute system. If not passed will use the
    50  // executable files name.
    51  func NewDefaultOptionsWCOW(id, owner string) *OptionsWCOW {
    52  	return &OptionsWCOW{
    53  		Options: newDefaultOptions(id, owner),
    54  	}
    55  }
    56  
    57  func (uvm *UtilityVM) startExternalGcsListener(ctx context.Context) error {
    58  	log.G(ctx).WithField("vmID", uvm.runtimeID).Debug("Using external GCS bridge")
    59  
    60  	l, err := winio.ListenHvsock(&winio.HvsockAddr{
    61  		VMID:      uvm.runtimeID,
    62  		ServiceID: gcs.WindowsGcsHvsockServiceID,
    63  	})
    64  	if err != nil {
    65  		return err
    66  	}
    67  	uvm.gcListener = l
    68  	return nil
    69  }
    70  
    71  func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW, uvmFolder string) (*hcsschema.ComputeSystem, error) {
    72  	processorTopology, err := processorinfo.HostProcessorInfo(ctx)
    73  	if err != nil {
    74  		return nil, fmt.Errorf("failed to get host processor information: %s", err)
    75  	}
    76  
    77  	// To maintain compatibility with Docker we need to automatically downgrade
    78  	// a user CPU count if the setting is not possible.
    79  	uvm.processorCount = uvm.normalizeProcessorCount(ctx, opts.ProcessorCount, processorTopology)
    80  
    81  	// Align the requested memory size.
    82  	memorySizeInMB := uvm.normalizeMemorySize(ctx, opts.MemorySizeInMB)
    83  
    84  	// UVM rootfs share is readonly.
    85  	vsmbOpts := uvm.DefaultVSMBOptions(true)
    86  	vsmbOpts.TakeBackupPrivilege = true
    87  	virtualSMB := &hcsschema.VirtualSmb{
    88  		DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere
    89  		Shares: []hcsschema.VirtualSmbShare{
    90  			{
    91  				Name:    "os",
    92  				Path:    filepath.Join(uvmFolder, `UtilityVM\Files`),
    93  				Options: vsmbOpts,
    94  			},
    95  		},
    96  	}
    97  
    98  	var registryChanges hcsschema.RegistryChanges
    99  	// We're getting asked to setup local dump collection for WCOW. We need to:
   100  	//
   101  	// 1. Turn off WER reporting, so we don't both upload the dump and save a local copy.
   102  	// 2. Set WerSvc to start when the UVM starts to work around a bug when generating dumps for certain exceptions.
   103  	// https://github.com/microsoft/Windows-Containers/issues/60#issuecomment-834633192
   104  	// This supposedly should be fixed soon but for now keep this until we know which container images
   105  	// (1809, 1903/9, 2004 etc.) this went out too.
   106  	if opts.ProcessDumpLocation != "" {
   107  		uvm.processDumpLocation = opts.ProcessDumpLocation
   108  		registryChanges.AddValues = append(registryChanges.AddValues,
   109  			hcsschema.RegistryValue{
   110  				Key: &hcsschema.RegistryKey{
   111  					Hive: "System",
   112  					Name: "ControlSet001\\Services\\WerSvc",
   113  				},
   114  				Name:       "Start",
   115  				DWordValue: 2,
   116  				Type_:      "DWord",
   117  			},
   118  			hcsschema.RegistryValue{
   119  				Key: &hcsschema.RegistryKey{
   120  					Hive: "Software",
   121  					Name: "Microsoft\\Windows\\Windows Error Reporting",
   122  				},
   123  				Name:       "Disabled",
   124  				DWordValue: 1,
   125  				Type_:      "DWord",
   126  			},
   127  		)
   128  	}
   129  
   130  	// Here for a temporary workaround until the need for setting this regkey is no more. To protect
   131  	// against any undesired behavior (such as some general networking scenarios ceasing to function)
   132  	// with a recent change to fix SMB share access in the UVM, this registry key will be checked to
   133  	// enable the change in question inside GNS.dll.
   134  	if !opts.DisableCompartmentNamespace {
   135  		registryChanges.AddValues = append(registryChanges.AddValues,
   136  			hcsschema.RegistryValue{
   137  				Key: &hcsschema.RegistryKey{
   138  					Hive: "System",
   139  					Name: "CurrentControlSet\\Services\\gns",
   140  				},
   141  				Name:       "EnableCompartmentNamespace",
   142  				DWordValue: 1,
   143  				Type_:      "DWord",
   144  			},
   145  		)
   146  	}
   147  
   148  	processor := &hcsschema.Processor2{
   149  		Count:  uvm.processorCount,
   150  		Limit:  opts.ProcessorLimit,
   151  		Weight: opts.ProcessorWeight,
   152  	}
   153  	// We can set a cpu group for the VM at creation time in recent builds.
   154  	if opts.CPUGroupID != "" {
   155  		if osversion.Build() < osversion.V21H1 {
   156  			return nil, errCPUGroupCreateNotSupported
   157  		}
   158  		processor.CpuGroup = &hcsschema.CpuGroup{Id: opts.CPUGroupID}
   159  	}
   160  
   161  	doc := &hcsschema.ComputeSystem{
   162  		Owner:                             uvm.owner,
   163  		SchemaVersion:                     schemaversion.SchemaV21(),
   164  		ShouldTerminateOnLastHandleClosed: true,
   165  		VirtualMachine: &hcsschema.VirtualMachine{
   166  			StopOnReset: true,
   167  			Chipset: &hcsschema.Chipset{
   168  				Uefi: &hcsschema.Uefi{
   169  					BootThis: &hcsschema.UefiBootEntry{
   170  						DevicePath: `\EFI\Microsoft\Boot\bootmgfw.efi`,
   171  						DeviceType: "VmbFs",
   172  					},
   173  				},
   174  			},
   175  			RegistryChanges: &registryChanges,
   176  			ComputeTopology: &hcsschema.Topology{
   177  				Memory: &hcsschema.Memory2{
   178  					SizeInMB:        memorySizeInMB,
   179  					AllowOvercommit: opts.AllowOvercommit,
   180  					// EnableHotHint is not compatible with physical.
   181  					EnableHotHint:        opts.AllowOvercommit,
   182  					EnableDeferredCommit: opts.EnableDeferredCommit,
   183  					LowMMIOGapInMB:       opts.LowMMIOGapInMB,
   184  					HighMMIOBaseInMB:     opts.HighMMIOBaseInMB,
   185  					HighMMIOGapInMB:      opts.HighMMIOGapInMB,
   186  				},
   187  				Processor: processor,
   188  			},
   189  			Devices: &hcsschema.Devices{
   190  				HvSocket: &hcsschema.HvSocket2{
   191  					HvSocketConfig: &hcsschema.HvSocketSystemConfig{
   192  						// Allow administrators and SYSTEM to bind to vsock sockets
   193  						// so that we can create a GCS log socket.
   194  						DefaultBindSecurityDescriptor: "D:P(A;;FA;;;SY)(A;;FA;;;BA)",
   195  					},
   196  				},
   197  				VirtualSmb: virtualSMB,
   198  			},
   199  		},
   200  	}
   201  
   202  	// Handle StorageQoS if set
   203  	if opts.StorageQoSBandwidthMaximum > 0 || opts.StorageQoSIopsMaximum > 0 {
   204  		doc.VirtualMachine.StorageQoS = &hcsschema.StorageQoS{
   205  			IopsMaximum:      opts.StorageQoSIopsMaximum,
   206  			BandwidthMaximum: opts.StorageQoSBandwidthMaximum,
   207  		}
   208  	}
   209  
   210  	// Set boot options
   211  	if opts.DumpDirectoryPath != "" {
   212  		if info, err := os.Stat(opts.DumpDirectoryPath); err != nil {
   213  			return nil, fmt.Errorf("failed to stat dump directory %s: %w", opts.DumpDirectoryPath, err)
   214  		} else if !info.IsDir() {
   215  			return nil, fmt.Errorf("dump directory path %s isn't a directory", opts.DumpDirectoryPath)
   216  		}
   217  		if err := security.GrantVmGroupAccessWithMask(opts.DumpDirectoryPath, security.AccessMaskAll); err != nil {
   218  			return nil, fmt.Errorf("failed to add SDL to dump directory: %w", err)
   219  		}
   220  		debugOpts := &hcsschema.DebugOptions{
   221  			BugcheckSavedStateFileName:            filepath.Join(opts.DumpDirectoryPath, fmt.Sprintf("%s-savedstate.vmrs", uvm.id)),
   222  			BugcheckNoCrashdumpSavedStateFileName: filepath.Join(opts.DumpDirectoryPath, fmt.Sprintf("%s-savedstate_nocrashdump.vmrs", uvm.id)),
   223  		}
   224  		doc.VirtualMachine.DebugOptions = debugOpts
   225  	}
   226  
   227  	return doc, nil
   228  }
   229  
   230  // CreateWCOW creates an HCS compute system representing a utility VM.
   231  //
   232  // WCOW Notes:
   233  //   - The scratch is always attached to SCSI 0:0
   234  func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error) {
   235  	ctx, span := oc.StartSpan(ctx, "uvm::CreateWCOW")
   236  	defer span.End()
   237  	defer func() { oc.SetSpanStatus(span, err) }()
   238  
   239  	if opts.ID == "" {
   240  		g, err := guid.NewV4()
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  		opts.ID = g.String()
   245  	}
   246  
   247  	span.AddAttributes(trace.StringAttribute(logfields.UVMID, opts.ID))
   248  	log.G(ctx).WithField("options", fmt.Sprintf("%+v", opts)).Debug("uvm::CreateWCOW options")
   249  
   250  	uvm := &UtilityVM{
   251  		id:                      opts.ID,
   252  		owner:                   opts.Owner,
   253  		operatingSystem:         "windows",
   254  		scsiControllerCount:     opts.SCSIControllerCount,
   255  		vsmbDirShares:           make(map[string]*VSMBShare),
   256  		vsmbFileShares:          make(map[string]*VSMBShare),
   257  		vpciDevices:             make(map[VPCIDeviceKey]*VPCIDevice),
   258  		noInheritHostTimezone:   opts.NoInheritHostTimezone,
   259  		physicallyBacked:        !opts.AllowOvercommit,
   260  		devicesPhysicallyBacked: opts.FullyPhysicallyBacked,
   261  		vsmbNoDirectMap:         opts.NoDirectMap,
   262  		noWritableFileShares:    opts.NoWritableFileShares,
   263  		createOpts:              *opts,
   264  	}
   265  
   266  	defer func() {
   267  		if err != nil {
   268  			uvm.Close()
   269  		}
   270  	}()
   271  
   272  	if err := verifyOptions(ctx, opts); err != nil {
   273  		return nil, errors.Wrap(err, errBadUVMOpts.Error())
   274  	}
   275  
   276  	uvmFolder, err := uvmfolder.LocateUVMFolder(ctx, opts.LayerFolders)
   277  	if err != nil {
   278  		return nil, fmt.Errorf("failed to locate utility VM folder from layer folders: %s", err)
   279  	}
   280  
   281  	// TODO: BUGBUG Remove this. @jhowardmsft
   282  	//       It should be the responsibility of the caller to do the creation and population.
   283  	//       - Update runhcs too (vm.go).
   284  	//       - Remove comment in function header
   285  	//       - Update tests that rely on this current behavior.
   286  	// Create the RW scratch in the top-most layer folder, creating the folder if it doesn't already exist.
   287  	scratchFolder := opts.LayerFolders[len(opts.LayerFolders)-1]
   288  
   289  	// Create the directory if it doesn't exist
   290  	if _, err := os.Stat(scratchFolder); os.IsNotExist(err) {
   291  		if err := os.MkdirAll(scratchFolder, 0777); err != nil {
   292  			return nil, fmt.Errorf("failed to create utility VM scratch folder: %s", err)
   293  		}
   294  	}
   295  
   296  	doc, err := prepareConfigDoc(ctx, uvm, opts, uvmFolder)
   297  	if err != nil {
   298  		return nil, fmt.Errorf("error in preparing config doc: %s", err)
   299  	}
   300  
   301  	// Create sandbox.vhdx in the scratch folder based on the template, granting the correct permissions to it
   302  	scratchPath := filepath.Join(scratchFolder, "sandbox.vhdx")
   303  	if _, err := os.Stat(scratchPath); os.IsNotExist(err) {
   304  		if err := wcow.CreateUVMScratch(ctx, uvmFolder, scratchFolder, uvm.id); err != nil {
   305  			return nil, fmt.Errorf("failed to create scratch: %s", err)
   306  		}
   307  	} else {
   308  		// Sandbox.vhdx exists, just need to grant vm access to it.
   309  		if err := wclayer.GrantVmAccess(ctx, uvm.id, scratchPath); err != nil {
   310  			return nil, errors.Wrap(err, "failed to grant vm access to scratch")
   311  		}
   312  	}
   313  
   314  	doc.VirtualMachine.Devices.Scsi = map[string]hcsschema.Scsi{}
   315  	for i := 0; i < int(uvm.scsiControllerCount); i++ {
   316  		doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[i]] = hcsschema.Scsi{
   317  			Attachments: make(map[string]hcsschema.Attachment),
   318  		}
   319  	}
   320  
   321  	doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"] = hcsschema.Attachment{
   322  
   323  		Path:  scratchPath,
   324  		Type_: "VirtualDisk",
   325  	}
   326  
   327  	uvm.scsiLocations[0][0] = newSCSIMount(uvm,
   328  		doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"].Path,
   329  		"",
   330  		doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"].Type_,
   331  		"",
   332  		1,
   333  		0,
   334  		0,
   335  		false,
   336  		false)
   337  
   338  	err = uvm.create(ctx, doc)
   339  	if err != nil {
   340  		return nil, fmt.Errorf("error while creating the compute system: %s", err)
   341  	}
   342  
   343  	if err = uvm.startExternalGcsListener(ctx); err != nil {
   344  		return nil, err
   345  	}
   346  
   347  	uvm.ncProxyClientAddress = opts.NetworkConfigProxy
   348  
   349  	return uvm, nil
   350  }
   351  

View as plain text