cdi.go 3.5 KB

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