...

Source file src/github.com/Microsoft/hcsshim/internal/hcsoci/resources_wcow.go

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

     1  //go:build windows
     2  // +build windows
     3  
     4  package hcsoci
     5  
     6  // Contains functions relating to a WCOW container, as opposed to a utility VM
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	specs "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  
    19  	"github.com/Microsoft/hcsshim/internal/cmd"
    20  	"github.com/Microsoft/hcsshim/internal/credentials"
    21  	"github.com/Microsoft/hcsshim/internal/guestpath"
    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/resources"
    26  	"github.com/Microsoft/hcsshim/internal/schemaversion"
    27  	"github.com/Microsoft/hcsshim/internal/uvm"
    28  	"github.com/Microsoft/hcsshim/internal/wclayer"
    29  )
    30  
    31  const wcowSandboxMountPath = "C:\\SandboxMounts"
    32  
    33  func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r *resources.Resources, isSandbox bool) error {
    34  	if coi.Spec == nil || coi.Spec.Windows == nil || coi.Spec.Windows.LayerFolders == nil {
    35  		return errors.New("field 'Spec.Windows.Layerfolders' is not populated")
    36  	}
    37  
    38  	scratchFolder := coi.Spec.Windows.LayerFolders[len(coi.Spec.Windows.LayerFolders)-1]
    39  
    40  	// TODO: Remove this code for auto-creation. Make the caller responsible.
    41  	// Create the directory for the RW scratch layer if it doesn't exist
    42  	if _, err := os.Stat(scratchFolder); os.IsNotExist(err) {
    43  		if err := os.MkdirAll(scratchFolder, 0777); err != nil {
    44  			return errors.Wrapf(err, "failed to auto-create container scratch folder %s", scratchFolder)
    45  		}
    46  	}
    47  
    48  	// Create sandbox.vhdx if it doesn't exist in the scratch folder. It's called sandbox.vhdx
    49  	// rather than scratch.vhdx as in the v1 schema, it's hard-coded in HCS.
    50  	if _, err := os.Stat(filepath.Join(scratchFolder, "sandbox.vhdx")); os.IsNotExist(err) {
    51  		if err := wclayer.CreateScratchLayer(ctx, scratchFolder, coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1]); err != nil {
    52  			return errors.Wrap(err, "failed to CreateSandboxLayer")
    53  		}
    54  	}
    55  
    56  	if coi.Spec.Root == nil {
    57  		coi.Spec.Root = &specs.Root{}
    58  	}
    59  
    60  	if coi.Spec.Root.Path == "" && (coi.HostingSystem != nil || coi.Spec.Windows.HyperV == nil) {
    61  		log.G(ctx).Debug("hcsshim::allocateWindowsResources mounting storage")
    62  		containerRootInUVM := r.ContainerRootInUVM()
    63  		containerRootPath, closer, err := layers.MountWCOWLayers(ctx, coi.actualID, coi.Spec.Windows.LayerFolders, containerRootInUVM, "", coi.HostingSystem)
    64  		if err != nil {
    65  			return errors.Wrap(err, "failed to mount container storage")
    66  		}
    67  		coi.Spec.Root.Path = containerRootPath
    68  		// If this is the pause container in a hypervisor-isolated pod, we can skip cleanup of
    69  		// layers, as that happens automatically when the UVM is terminated.
    70  		if !isSandbox || coi.HostingSystem == nil {
    71  			r.SetLayers(closer)
    72  		}
    73  	}
    74  
    75  	if err := setupMounts(ctx, coi, r); err != nil {
    76  		return err
    77  	}
    78  
    79  	if cs, ok := coi.Spec.Windows.CredentialSpec.(string); ok {
    80  		// Only need to create a CCG instance for v2 containers
    81  		if schemaversion.IsV21(coi.actualSchemaVersion) {
    82  			hypervisorIsolated := coi.HostingSystem != nil
    83  			ccgInstance, ccgResource, err := credentials.CreateCredentialGuard(ctx, coi.actualID, cs, hypervisorIsolated)
    84  			if err != nil {
    85  				return err
    86  			}
    87  			coi.ccgState = ccgInstance.CredentialGuard
    88  			r.Add(ccgResource)
    89  			if hypervisorIsolated {
    90  				// If hypervisor isolated we need to add an hvsocket service table entry
    91  				// By default HVSocket won't allow something inside the VM to connect
    92  				// back to a process on the host. We need to update the HVSocket service table
    93  				// to allow a connection to CCG.exe on the host, so that GMSA can function.
    94  				// We need to hot add this here because at UVM creation time we don't know what containers
    95  				// will be launched in the UVM, nonetheless if they will ask for GMSA. This is a workaround
    96  				// for the previous design requirement for CCG V2 where the service entry
    97  				// must be present in the UVM'S HCS document before being sent over as hot adding
    98  				// an HvSocket service was not possible.
    99  				hvSockConfig := ccgInstance.HvSocketConfig
   100  				if err := coi.HostingSystem.UpdateHvSocketService(ctx, hvSockConfig.ServiceId, hvSockConfig.ServiceConfig); err != nil {
   101  					return errors.Wrap(err, "failed to update hvsocket service")
   102  				}
   103  			}
   104  		}
   105  	}
   106  
   107  	if coi.HostingSystem != nil {
   108  		if coi.hasWindowsAssignedDevices() {
   109  			windowsDevices, closers, err := handleAssignedDevicesWindows(ctx, coi.HostingSystem, coi.Spec.Annotations, coi.Spec.Windows.Devices)
   110  			if err != nil {
   111  				return err
   112  			}
   113  			r.Add(closers...)
   114  			coi.Spec.Windows.Devices = windowsDevices
   115  		}
   116  		// when driver installation completes, we are guaranteed that the device is ready for use,
   117  		// so reinstall drivers to make sure the devices are ready when we proceed.
   118  		// TODO katiewasnothere: we should find a way to avoid reinstalling drivers
   119  		driverClosers, err := addSpecGuestDrivers(ctx, coi.HostingSystem, coi.Spec.Annotations)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		r.Add(driverClosers...)
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  // setupMounts adds the custom mounts requested in the container configuration of this
   130  // request.
   131  func setupMounts(ctx context.Context, coi *createOptionsInternal, r *resources.Resources) error {
   132  	// Validate each of the mounts. If this is a V2 Xenon, we have to add them as
   133  	// VSMB shares to the utility VM. For V1 Xenon and Argons, there's nothing for
   134  	// us to do as it's done by HCS.
   135  	for _, mount := range coi.Spec.Mounts {
   136  		if mount.Destination == "" || mount.Source == "" {
   137  			return fmt.Errorf("invalid OCI spec - a mount must have both source and a destination: %+v", mount)
   138  		}
   139  		switch mount.Type {
   140  		case "":
   141  		case MountTypePhysicalDisk:
   142  		case MountTypeVirtualDisk:
   143  		case MountTypeExtensibleVirtualDisk:
   144  		default:
   145  			return fmt.Errorf("invalid OCI spec - Type '%s' not supported", mount.Type)
   146  		}
   147  
   148  		if coi.HostingSystem != nil && schemaversion.IsV21(coi.actualSchemaVersion) {
   149  			uvmPath := fmt.Sprintf(guestpath.WCOWGlobalMountPrefixFmt, coi.HostingSystem.UVMMountCounter())
   150  			readOnly := false
   151  			for _, o := range mount.Options {
   152  				if strings.ToLower(o) == "ro" {
   153  					readOnly = true
   154  					break
   155  				}
   156  			}
   157  			l := log.G(ctx).WithField("mount", fmt.Sprintf("%+v", mount))
   158  			if mount.Type == MountTypePhysicalDisk || mount.Type == MountTypeVirtualDisk || mount.Type == MountTypeExtensibleVirtualDisk {
   159  				var (
   160  					scsiMount *uvm.SCSIMount
   161  					err       error
   162  				)
   163  				switch mount.Type {
   164  				case MountTypePhysicalDisk:
   165  					l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI physical disk for OCI mount")
   166  					scsiMount, err = coi.HostingSystem.AddSCSIPhysicalDisk(
   167  						ctx,
   168  						mount.Source,
   169  						uvmPath,
   170  						readOnly,
   171  						mount.Options,
   172  					)
   173  				case MountTypeVirtualDisk:
   174  					l.Debug("hcsshim::allocateWindowsResources Hot-adding SCSI virtual disk for OCI mount")
   175  					scsiMount, err = coi.HostingSystem.AddSCSI(
   176  						ctx,
   177  						mount.Source,
   178  						uvmPath,
   179  						readOnly,
   180  						false,
   181  						mount.Options,
   182  						uvm.VMAccessTypeIndividual,
   183  					)
   184  				case MountTypeExtensibleVirtualDisk:
   185  					l.Debug("hcsshim::allocateWindowsResource Hot-adding ExtensibleVirtualDisk")
   186  					scsiMount, err = coi.HostingSystem.AddSCSIExtensibleVirtualDisk(
   187  						ctx,
   188  						mount.Source,
   189  						uvmPath,
   190  						readOnly,
   191  					)
   192  				}
   193  				if err != nil {
   194  					return fmt.Errorf("adding SCSI mount %+v: %w", mount, err)
   195  				}
   196  				r.Add(scsiMount)
   197  				// Compute guest mounts now, and store them, so they can be added to the container doc later.
   198  				// We do this now because we have access to the guest path through the returned mount object.
   199  				coi.windowsAdditionalMounts = append(coi.windowsAdditionalMounts, hcsschema.MappedDirectory{
   200  					HostPath:      scsiMount.UVMPath,
   201  					ContainerPath: mount.Destination,
   202  					ReadOnly:      readOnly,
   203  				})
   204  			} else if strings.HasPrefix(mount.Source, guestpath.SandboxMountPrefix) {
   205  				// Mounts that map to a path in the UVM are specified with a 'sandbox://' prefix.
   206  				//
   207  				// Example: sandbox:///a/dirInUvm destination:C:\\dirInContainer.
   208  				//
   209  				// so first convert to a path in the sandboxmounts path itself.
   210  				sandboxPath := convertToWCOWSandboxMountPath(mount.Source)
   211  
   212  				// Now we need to exec a process in the vm that will make these directories as theres
   213  				// no functionality in the Windows gcs to create an arbitrary directory.
   214  				//
   215  				// Create the directory, but also run dir afterwards regardless of if mkdir succeeded to handle the case where the directory already exists
   216  				// e.g. from a previous container specifying the same mount (and thus creating the same directory).
   217  				b := &bytes.Buffer{}
   218  				stderr, err := cmd.CreatePipeAndListen(b, false)
   219  				if err != nil {
   220  					return err
   221  				}
   222  				req := &cmd.CmdProcessRequest{
   223  					Args:   []string{"cmd", "/c", "mkdir", sandboxPath, "&", "dir", sandboxPath},
   224  					Stderr: stderr,
   225  				}
   226  				exitCode, err := cmd.ExecInUvm(ctx, coi.HostingSystem, req)
   227  				if err != nil {
   228  					return errors.Wrapf(err, "failed to create sandbox mount directory in utility VM with exit code %d %q", exitCode, b.String())
   229  				}
   230  			} else {
   231  				if uvm.IsPipe(mount.Source) {
   232  					pipe, err := coi.HostingSystem.AddPipe(ctx, mount.Source)
   233  					if err != nil {
   234  						return errors.Wrap(err, "failed to add named pipe to UVM")
   235  					}
   236  					r.Add(pipe)
   237  				} else {
   238  					l.Debug("hcsshim::allocateWindowsResources Hot-adding VSMB share for OCI mount")
   239  					options := coi.HostingSystem.DefaultVSMBOptions(readOnly)
   240  					share, err := coi.HostingSystem.AddVSMB(ctx, mount.Source, options)
   241  					if err != nil {
   242  						return errors.Wrapf(err, "failed to add VSMB share to utility VM for mount %+v", mount)
   243  					}
   244  					r.Add(share)
   245  				}
   246  			}
   247  		}
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  func convertToWCOWSandboxMountPath(source string) string {
   254  	subPath := strings.TrimPrefix(source, guestpath.SandboxMountPrefix)
   255  	return filepath.Join(wcowSandboxMountPath, subPath)
   256  }
   257  

View as plain text