github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/cdi.go (about)

     1  package daemon
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/containerd/log"
     8  	"github.com/docker/docker/errdefs"
     9  	"github.com/hashicorp/go-multierror"
    10  	specs "github.com/opencontainers/runtime-spec/specs-go"
    11  	"github.com/pkg/errors"
    12  	"tags.cncf.io/container-device-interface/pkg/cdi"
    13  )
    14  
    15  type cdiHandler struct {
    16  	registry *cdi.Cache
    17  }
    18  
    19  // RegisterCDIDriver registers the CDI device driver.
    20  // The driver injects CDI devices into an incoming OCI spec and is called for DeviceRequests associated with CDI devices.
    21  // If the list of CDI spec directories is empty, the driver is not registered.
    22  func RegisterCDIDriver(cdiSpecDirs ...string) {
    23  	driver := newCDIDeviceDriver(cdiSpecDirs...)
    24  
    25  	registerDeviceDriver("cdi", driver)
    26  }
    27  
    28  // newCDIDeviceDriver creates a new CDI device driver.
    29  // If the creation of the CDI cache fails, a driver is returned that will return an error on an injection request.
    30  func newCDIDeviceDriver(cdiSpecDirs ...string) *deviceDriver {
    31  	cache, err := createCDICache(cdiSpecDirs...)
    32  	if err != nil {
    33  		log.G(context.TODO()).WithError(err)
    34  		// We create a spec updater that always returns an error.
    35  		// This error will be returned only when a CDI device is requested.
    36  		// This ensures that daemon startup is not blocked by a CDI registry initialization failure or being disabled
    37  		// by configuratrion.
    38  		errorOnUpdateSpec := func(s *specs.Spec, dev *deviceInstance) error {
    39  			return fmt.Errorf("CDI device injection failed: %w", err)
    40  		}
    41  		return &deviceDriver{
    42  			updateSpec: errorOnUpdateSpec,
    43  		}
    44  	}
    45  
    46  	// We construct a spec updates that injects CDI devices into the OCI spec using the initialized registry.
    47  	c := &cdiHandler{
    48  		registry: cache,
    49  	}
    50  
    51  	return &deviceDriver{
    52  		updateSpec: c.injectCDIDevices,
    53  	}
    54  }
    55  
    56  // createCDICache creates a CDI cache for the specified CDI specification directories.
    57  // If the list of CDI specification directories is empty or the creation of the CDI cache fails, an error is returned.
    58  func createCDICache(cdiSpecDirs ...string) (*cdi.Cache, error) {
    59  	if len(cdiSpecDirs) == 0 {
    60  		return nil, fmt.Errorf("No CDI specification directories specified")
    61  	}
    62  
    63  	cache, err := cdi.NewCache(cdi.WithSpecDirs(cdiSpecDirs...))
    64  	if err != nil {
    65  		return nil, fmt.Errorf("CDI registry initialization failure: %w", err)
    66  	}
    67  
    68  	return cache, nil
    69  }
    70  
    71  // injectCDIDevices injects a set of CDI devices into the specified OCI specification.
    72  func (c *cdiHandler) injectCDIDevices(s *specs.Spec, dev *deviceInstance) error {
    73  	if dev.req.Count != 0 {
    74  		return errdefs.InvalidParameter(errors.New("unexpected count in CDI device request"))
    75  	}
    76  	if len(dev.req.Options) > 0 {
    77  		return errdefs.InvalidParameter(errors.New("unexpected options in CDI device request"))
    78  	}
    79  
    80  	cdiDeviceNames := dev.req.DeviceIDs
    81  	if len(cdiDeviceNames) == 0 {
    82  		return nil
    83  	}
    84  
    85  	_, err := c.registry.InjectDevices(s, cdiDeviceNames...)
    86  	if err != nil {
    87  		if rerrs := c.getErrors(); rerrs != nil {
    88  			// We log the errors that may have been generated while refreshing the CDI registry.
    89  			// These may be due to malformed specifications or device name conflicts that could be
    90  			// the cause of an injection failure.
    91  			log.G(context.TODO()).WithError(rerrs).Warning("Refreshing the CDI registry generated errors")
    92  		}
    93  
    94  		return fmt.Errorf("CDI device injection failed: %w", err)
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // getErrors returns a single error representation of errors that may have occurred while refreshing the CDI registry.
   101  func (c *cdiHandler) getErrors() error {
   102  	errors := c.registry.GetErrors()
   103  
   104  	var err *multierror.Error
   105  	for _, errs := range errors {
   106  		err = multierror.Append(err, errs...)
   107  	}
   108  	return err.ErrorOrNil()
   109  }