github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/daemon/graphdriver/lcow/lcow_svm.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow"
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/Microsoft/hcsshim"
    15  	"github.com/Microsoft/opengcs/client"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // Code for all the service VM management for the LCOW graphdriver
    21  
    22  var errVMisTerminating = errors.New("service VM is shutting down")
    23  var errVMUnknown = errors.New("service vm id is unknown")
    24  var errVMStillHasReference = errors.New("Attemping to delete a VM that is still being used")
    25  
    26  // serviceVMMap is the struct representing the id -> service VM mapping.
    27  type serviceVMMap struct {
    28  	sync.Mutex
    29  	svms map[string]*serviceVMMapItem
    30  }
    31  
    32  // serviceVMMapItem is our internal structure representing an item in our
    33  // map of service VMs we are maintaining.
    34  type serviceVMMapItem struct {
    35  	svm      *serviceVM // actual service vm object
    36  	refCount int        // refcount for VM
    37  }
    38  
    39  // attachedVHD is for reference counting SCSI disks attached to a service VM,
    40  // and for a counter used to generate a short path name for the container path.
    41  type attachedVHD struct {
    42  	refCount      int
    43  	attachCounter uint64
    44  }
    45  
    46  type serviceVM struct {
    47  	sync.Mutex                     // Serialises operations being performed in this service VM.
    48  	scratchAttached bool           // Has a scratch been attached?
    49  	config          *client.Config // Represents the service VM item.
    50  
    51  	// Indicates that the vm is started
    52  	startStatus chan interface{}
    53  	startError  error
    54  
    55  	// Indicates that the vm is stopped
    56  	stopStatus chan interface{}
    57  	stopError  error
    58  
    59  	attachCounter uint64                  // Increasing counter for each add
    60  	attachedVHDs  map[string]*attachedVHD // Map ref counting all the VHDS we've hot-added/hot-removed.
    61  	unionMounts   map[string]int          // Map ref counting all the union filesystems we mounted.
    62  }
    63  
    64  // add will add an id to the service vm map. There are three cases:
    65  //   - entry doesn't exist:
    66  //     add id to map and return a new vm that the caller can manually configure+start
    67  //   - entry does exist:
    68  //     return vm in map and increment ref count
    69  //   - entry does exist but the ref count is 0:
    70  //     return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop
    71  func (svmMap *serviceVMMap) add(id string) (svm *serviceVM, alreadyExists bool, err error) {
    72  	svmMap.Lock()
    73  	defer svmMap.Unlock()
    74  	if svm, ok := svmMap.svms[id]; ok {
    75  		if svm.refCount == 0 {
    76  			return svm.svm, true, errVMisTerminating
    77  		}
    78  		svm.refCount++
    79  		return svm.svm, true, nil
    80  	}
    81  
    82  	// Doesn't exist, so create an empty svm to put into map and return
    83  	newSVM := &serviceVM{
    84  		startStatus:  make(chan interface{}),
    85  		stopStatus:   make(chan interface{}),
    86  		attachedVHDs: make(map[string]*attachedVHD),
    87  		unionMounts:  make(map[string]int),
    88  		config:       &client.Config{},
    89  	}
    90  	svmMap.svms[id] = &serviceVMMapItem{
    91  		svm:      newSVM,
    92  		refCount: 1,
    93  	}
    94  	return newSVM, false, nil
    95  }
    96  
    97  // get will get the service vm from the map. There are three cases:
    98  //   - entry doesn't exist:
    99  //     return errVMUnknown
   100  //   - entry does exist:
   101  //     return vm with no error
   102  //   - entry does exist but the ref count is 0:
   103  //     return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop
   104  func (svmMap *serviceVMMap) get(id string) (*serviceVM, error) {
   105  	svmMap.Lock()
   106  	defer svmMap.Unlock()
   107  	svm, ok := svmMap.svms[id]
   108  	if !ok {
   109  		return nil, errVMUnknown
   110  	}
   111  	if svm.refCount == 0 {
   112  		return svm.svm, errVMisTerminating
   113  	}
   114  	return svm.svm, nil
   115  }
   116  
   117  // decrementRefCount decrements the ref count of the given ID from the map. There are four cases:
   118  //   - entry doesn't exist:
   119  //     return errVMUnknown
   120  //   - entry does exist but the ref count is 0:
   121  //     return the svm and errVMisTerminating. Caller can call svm.getStopError() to wait for stop
   122  //   - entry does exist but ref count is 1:
   123  //     return vm and set lastRef to true. The caller can then stop the vm, delete the id from this map
   124  //     and execute svm.signalStopFinished to signal the threads that the svm has been terminated.
   125  //   - entry does exist and ref count > 1:
   126  //     just reduce ref count and return svm
   127  func (svmMap *serviceVMMap) decrementRefCount(id string) (_ *serviceVM, lastRef bool, _ error) {
   128  	svmMap.Lock()
   129  	defer svmMap.Unlock()
   130  
   131  	svm, ok := svmMap.svms[id]
   132  	if !ok {
   133  		return nil, false, errVMUnknown
   134  	}
   135  	if svm.refCount == 0 {
   136  		return svm.svm, false, errVMisTerminating
   137  	}
   138  	svm.refCount--
   139  	return svm.svm, svm.refCount == 0, nil
   140  }
   141  
   142  // setRefCountZero works the same way as decrementRefCount, but sets ref count to 0 instead of decrementing it.
   143  func (svmMap *serviceVMMap) setRefCountZero(id string) (*serviceVM, error) {
   144  	svmMap.Lock()
   145  	defer svmMap.Unlock()
   146  
   147  	svm, ok := svmMap.svms[id]
   148  	if !ok {
   149  		return nil, errVMUnknown
   150  	}
   151  	if svm.refCount == 0 {
   152  		return svm.svm, errVMisTerminating
   153  	}
   154  	svm.refCount = 0
   155  	return svm.svm, nil
   156  }
   157  
   158  // deleteID deletes the given ID from the map. If the refcount is not 0 or the
   159  // VM does not exist, then this function returns an error.
   160  func (svmMap *serviceVMMap) deleteID(id string) error {
   161  	svmMap.Lock()
   162  	defer svmMap.Unlock()
   163  	svm, ok := svmMap.svms[id]
   164  	if !ok {
   165  		return errVMUnknown
   166  	}
   167  	if svm.refCount != 0 {
   168  		return errVMStillHasReference
   169  	}
   170  	delete(svmMap.svms, id)
   171  	return nil
   172  }
   173  
   174  func (svm *serviceVM) signalStartFinished(err error) {
   175  	svm.Lock()
   176  	svm.startError = err
   177  	svm.Unlock()
   178  	close(svm.startStatus)
   179  }
   180  
   181  func (svm *serviceVM) getStartError() error {
   182  	<-svm.startStatus
   183  	svm.Lock()
   184  	defer svm.Unlock()
   185  	return svm.startError
   186  }
   187  
   188  func (svm *serviceVM) signalStopFinished(err error) {
   189  	svm.Lock()
   190  	svm.stopError = err
   191  	svm.Unlock()
   192  	close(svm.stopStatus)
   193  }
   194  
   195  func (svm *serviceVM) getStopError() error {
   196  	<-svm.stopStatus
   197  	svm.Lock()
   198  	defer svm.Unlock()
   199  	return svm.stopError
   200  }
   201  
   202  // hotAddVHDs waits for the service vm to start and then attaches the vhds.
   203  func (svm *serviceVM) hotAddVHDs(mvds ...hcsshim.MappedVirtualDisk) error {
   204  	if err := svm.getStartError(); err != nil {
   205  		return err
   206  	}
   207  	return svm.hotAddVHDsAtStart(mvds...)
   208  }
   209  
   210  // hotAddVHDsAtStart works the same way as hotAddVHDs but does not wait for the VM to start.
   211  func (svm *serviceVM) hotAddVHDsAtStart(mvds ...hcsshim.MappedVirtualDisk) error {
   212  	svm.Lock()
   213  	defer svm.Unlock()
   214  	for i, mvd := range mvds {
   215  		if _, ok := svm.attachedVHDs[mvd.HostPath]; ok {
   216  			svm.attachedVHDs[mvd.HostPath].refCount++
   217  			logrus.Debugf("lcowdriver: UVM %s: %s already present, refCount now %d", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount)
   218  			continue
   219  		}
   220  
   221  		svm.attachCounter++
   222  		shortContainerPath := remapLongToShortContainerPath(mvd.ContainerPath, svm.attachCounter, svm.config.Name)
   223  		if err := svm.config.HotAddVhd(mvd.HostPath, shortContainerPath, mvd.ReadOnly, !mvd.AttachOnly); err != nil {
   224  			svm.hotRemoveVHDsNoLock(mvds[:i]...)
   225  			return err
   226  		}
   227  		svm.attachedVHDs[mvd.HostPath] = &attachedVHD{refCount: 1, attachCounter: svm.attachCounter}
   228  	}
   229  	return nil
   230  }
   231  
   232  // hotRemoveVHDs waits for the service vm to start and then removes the vhds.
   233  // The service VM must not be locked when calling this function.
   234  func (svm *serviceVM) hotRemoveVHDs(mvds ...hcsshim.MappedVirtualDisk) error {
   235  	if err := svm.getStartError(); err != nil {
   236  		return err
   237  	}
   238  	svm.Lock()
   239  	defer svm.Unlock()
   240  	return svm.hotRemoveVHDsNoLock(mvds...)
   241  }
   242  
   243  // hotRemoveVHDsNoLock removes VHDs from a service VM. When calling this function,
   244  // the contract is the service VM lock must be held.
   245  func (svm *serviceVM) hotRemoveVHDsNoLock(mvds ...hcsshim.MappedVirtualDisk) error {
   246  	var retErr error
   247  	for _, mvd := range mvds {
   248  		if _, ok := svm.attachedVHDs[mvd.HostPath]; !ok {
   249  			// We continue instead of returning an error if we try to hot remove a non-existent VHD.
   250  			// This is because one of the callers of the function is graphdriver.Put(). Since graphdriver.Get()
   251  			// defers the VM start to the first operation, it's possible that nothing have been hot-added
   252  			// when Put() is called. To avoid Put returning an error in that case, we simply continue if we
   253  			// don't find the vhd attached.
   254  			logrus.Debugf("lcowdriver: UVM %s: %s is not attached, not doing anything", svm.config.Name, mvd.HostPath)
   255  			continue
   256  		}
   257  
   258  		if svm.attachedVHDs[mvd.HostPath].refCount > 1 {
   259  			svm.attachedVHDs[mvd.HostPath].refCount--
   260  			logrus.Debugf("lcowdriver: UVM %s: %s refCount dropped to %d. not removing from UVM", svm.config.Name, mvd.HostPath, svm.attachedVHDs[mvd.HostPath].refCount)
   261  			continue
   262  		}
   263  
   264  		// last reference to VHD, so remove from VM and map
   265  		if err := svm.config.HotRemoveVhd(mvd.HostPath); err == nil {
   266  			delete(svm.attachedVHDs, mvd.HostPath)
   267  		} else {
   268  			// Take note of the error, but still continue to remove the other VHDs
   269  			logrus.Warnf("Failed to hot remove %s: %s", mvd.HostPath, err)
   270  			if retErr == nil {
   271  				retErr = err
   272  			}
   273  		}
   274  	}
   275  	return retErr
   276  }
   277  
   278  func (svm *serviceVM) createExt4VHDX(destFile string, sizeGB uint32, cacheFile string) error {
   279  	if err := svm.getStartError(); err != nil {
   280  		return err
   281  	}
   282  
   283  	svm.Lock()
   284  	defer svm.Unlock()
   285  	return svm.config.CreateExt4Vhdx(destFile, sizeGB, cacheFile)
   286  }
   287  
   288  // getShortContainerPath looks up where a SCSI disk was actually mounted
   289  // in a service VM when we remapped a long path name to a short name.
   290  func (svm *serviceVM) getShortContainerPath(mvd *hcsshim.MappedVirtualDisk) string {
   291  	if mvd.ContainerPath == "" {
   292  		return ""
   293  	}
   294  	avhd, ok := svm.attachedVHDs[mvd.HostPath]
   295  	if !ok {
   296  		return ""
   297  	}
   298  	return fmt.Sprintf("/tmp/d%d", avhd.attachCounter)
   299  }
   300  
   301  func (svm *serviceVM) createUnionMount(mountName string, mvds ...hcsshim.MappedVirtualDisk) (err error) {
   302  	if len(mvds) == 0 {
   303  		return fmt.Errorf("createUnionMount: error must have at least 1 layer")
   304  	}
   305  
   306  	if err = svm.getStartError(); err != nil {
   307  		return err
   308  	}
   309  
   310  	svm.Lock()
   311  	defer svm.Unlock()
   312  	if _, ok := svm.unionMounts[mountName]; ok {
   313  		svm.unionMounts[mountName]++
   314  		return nil
   315  	}
   316  
   317  	var lowerLayers []string
   318  	if mvds[0].ReadOnly {
   319  		lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[0]))
   320  	}
   321  
   322  	for i := 1; i < len(mvds); i++ {
   323  		lowerLayers = append(lowerLayers, svm.getShortContainerPath(&mvds[i]))
   324  	}
   325  
   326  	logrus.Debugf("Doing the overlay mount with union directory=%s", mountName)
   327  	errOut := &bytes.Buffer{}
   328  	if err = svm.runProcess(fmt.Sprintf("mkdir -p %s", mountName), nil, nil, errOut); err != nil {
   329  		return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String())
   330  	}
   331  
   332  	var cmd string
   333  	if len(mvds) == 1 {
   334  		// `FROM SCRATCH` case and the only layer. No overlay required.
   335  		cmd = fmt.Sprintf("mount %s %s", svm.getShortContainerPath(&mvds[0]), mountName)
   336  	} else if mvds[0].ReadOnly {
   337  		// Readonly overlay
   338  		cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s %s",
   339  			strings.Join(lowerLayers, ","),
   340  			mountName)
   341  	} else {
   342  		upper := fmt.Sprintf("%s/upper", svm.getShortContainerPath(&mvds[0]))
   343  		work := fmt.Sprintf("%s/work", svm.getShortContainerPath(&mvds[0]))
   344  
   345  		errOut := &bytes.Buffer{}
   346  		if err = svm.runProcess(fmt.Sprintf("mkdir -p %s %s", upper, work), nil, nil, errOut); err != nil {
   347  			return errors.Wrapf(err, "mkdir -p %s failed (%s)", mountName, errOut.String())
   348  		}
   349  
   350  		cmd = fmt.Sprintf("mount -t overlay overlay -olowerdir=%s,upperdir=%s,workdir=%s %s",
   351  			strings.Join(lowerLayers, ":"),
   352  			upper,
   353  			work,
   354  			mountName)
   355  	}
   356  
   357  	logrus.Debugf("createUnionMount: Executing mount=%s", cmd)
   358  	errOut = &bytes.Buffer{}
   359  	if err = svm.runProcess(cmd, nil, nil, errOut); err != nil {
   360  		return errors.Wrapf(err, "%s failed (%s)", cmd, errOut.String())
   361  	}
   362  
   363  	svm.unionMounts[mountName] = 1
   364  	return nil
   365  }
   366  
   367  func (svm *serviceVM) deleteUnionMount(mountName string, disks ...hcsshim.MappedVirtualDisk) error {
   368  	if err := svm.getStartError(); err != nil {
   369  		return err
   370  	}
   371  
   372  	svm.Lock()
   373  	defer svm.Unlock()
   374  	if _, ok := svm.unionMounts[mountName]; !ok {
   375  		return nil
   376  	}
   377  
   378  	if svm.unionMounts[mountName] > 1 {
   379  		svm.unionMounts[mountName]--
   380  		return nil
   381  	}
   382  
   383  	logrus.Debugf("Removing union mount %s", mountName)
   384  	if err := svm.runProcess(fmt.Sprintf("umount %s", mountName), nil, nil, nil); err != nil {
   385  		return err
   386  	}
   387  
   388  	delete(svm.unionMounts, mountName)
   389  	return nil
   390  }
   391  
   392  func (svm *serviceVM) runProcess(command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
   393  	var process hcsshim.Process
   394  	var err error
   395  	errOut := &bytes.Buffer{}
   396  
   397  	if stderr != nil {
   398  		process, err = svm.config.RunProcess(command, stdin, stdout, stderr)
   399  	} else {
   400  		process, err = svm.config.RunProcess(command, stdin, stdout, errOut)
   401  	}
   402  	if err != nil {
   403  		return err
   404  	}
   405  	defer process.Close()
   406  
   407  	process.WaitTimeout(time.Duration(int(time.Second) * svm.config.UvmTimeoutSeconds))
   408  	exitCode, err := process.ExitCode()
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	if exitCode != 0 {
   414  		// If the caller isn't explicitly capturing stderr output, then capture it here instead.
   415  		e := fmt.Sprintf("svm.runProcess: command %s failed with exit code %d", command, exitCode)
   416  		if stderr == nil {
   417  			e = fmt.Sprintf("%s. (%s)", e, errOut.String())
   418  		}
   419  		return fmt.Errorf(e)
   420  	}
   421  	return nil
   422  }