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 }