doc.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // Package cdi has the primary purpose of providing an API for
  2. // interacting with CDI and consuming CDI devices.
  3. //
  4. // For more information about Container Device Interface, please refer to
  5. // https://github.com/container-orchestrated-devices/container-device-interface
  6. //
  7. // Container Device Interface
  8. //
  9. // Container Device Interface, or CDI for short, provides comprehensive
  10. // third party device support for container runtimes. CDI uses vendor
  11. // provided specification files, CDI Specs for short, to describe how a
  12. // container's runtime environment should be modified when one or more
  13. // of the vendor-specific devices is injected into the container. Beyond
  14. // describing the low level platform-specific details of how to gain
  15. // basic access to a device, CDI Specs allow more fine-grained device
  16. // initialization, and the automatic injection of any necessary vendor-
  17. // or device-specific software that might be required for a container
  18. // to use a device or take full advantage of it.
  19. //
  20. // In the CDI device model containers request access to a device using
  21. // fully qualified device names, qualified names for short, consisting of
  22. // a vendor identifier, a device class and a device name or identifier.
  23. // These pieces of information together uniquely identify a device among
  24. // all device vendors, classes and device instances.
  25. //
  26. // This package implements an API for easy consumption of CDI. The API
  27. // implements discovery, loading and caching of CDI Specs and injection
  28. // of CDI devices into containers. This is the most common functionality
  29. // the vast majority of CDI consumers need. The API should be usable both
  30. // by OCI runtime clients and runtime implementations.
  31. //
  32. // CDI Registry
  33. //
  34. // The primary interface to interact with CDI devices is the Registry. It
  35. // is essentially a cache of all Specs and devices discovered in standard
  36. // CDI directories on the host. The registry has two main functionality,
  37. // injecting devices into an OCI Spec and refreshing the cache of CDI
  38. // Specs and devices.
  39. //
  40. // Device Injection
  41. //
  42. // Using the Registry one can inject CDI devices into a container with code
  43. // similar to the following snippet:
  44. //
  45. // import (
  46. // "fmt"
  47. // "strings"
  48. //
  49. // log "github.com/sirupsen/logrus"
  50. //
  51. // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
  52. // oci "github.com/opencontainers/runtime-spec/specs-go"
  53. // )
  54. //
  55. // func injectCDIDevices(spec *oci.Spec, devices []string) error {
  56. // log.Debug("pristine OCI Spec: %s", dumpSpec(spec))
  57. //
  58. // unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices)
  59. // if err != nil {
  60. // return fmt.Errorf("CDI device injection failed: %w", err)
  61. // }
  62. //
  63. // log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
  64. // return nil
  65. // }
  66. //
  67. // Cache Refresh
  68. //
  69. // By default the CDI Spec cache monitors the configured Spec directories
  70. // and automatically refreshes itself when necessary. This behavior can be
  71. // disabled using the WithAutoRefresh(false) option.
  72. //
  73. // Failure to set up monitoring for a Spec directory causes the directory to
  74. // get ignored and an error to be recorded among the Spec directory errors.
  75. // These errors can be queried using the GetSpecDirErrors() function. If the
  76. // error condition is transient, for instance a missing directory which later
  77. // gets created, the corresponding error will be removed once the condition
  78. // is over.
  79. //
  80. // With auto-refresh enabled injecting any CDI devices can be done without
  81. // an explicit call to Refresh(), using a code snippet similar to the
  82. // following:
  83. //
  84. // In a runtime implementation one typically wants to make sure the
  85. // CDI Spec cache is up to date before performing device injection.
  86. // A code snippet similar to the following accmplishes that:
  87. //
  88. // import (
  89. // "fmt"
  90. // "strings"
  91. //
  92. // log "github.com/sirupsen/logrus"
  93. //
  94. // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
  95. // oci "github.com/opencontainers/runtime-spec/specs-go"
  96. // )
  97. //
  98. // func injectCDIDevices(spec *oci.Spec, devices []string) error {
  99. // registry := cdi.GetRegistry()
  100. //
  101. // if err := registry.Refresh(); err != nil {
  102. // // Note:
  103. // // It is up to the implementation to decide whether
  104. // // to abort injection on errors. A failed Refresh()
  105. // // does not necessarily render the registry unusable.
  106. // // For instance, a parse error in a Spec file for
  107. // // vendor A does not have any effect on devices of
  108. // // vendor B...
  109. // log.Warnf("pre-injection Refresh() failed: %v", err)
  110. // }
  111. //
  112. // log.Debug("pristine OCI Spec: %s", dumpSpec(spec))
  113. //
  114. // unresolved, err := registry.InjectDevices(spec, devices)
  115. // if err != nil {
  116. // return fmt.Errorf("CDI device injection failed: %w", err)
  117. // }
  118. //
  119. // log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
  120. // return nil
  121. // }
  122. //
  123. // Generated Spec Files, Multiple Directories, Device Precedence
  124. //
  125. // It is often necessary to generate Spec files dynamically. On some
  126. // systems the available or usable set of CDI devices might change
  127. // dynamically which then needs to be reflected in CDI Specs. For
  128. // some device classes it makes sense to enumerate the available
  129. // devices at every boot and generate Spec file entries for each
  130. // device found. Some CDI devices might need special client- or
  131. // request-specific configuration which can only be fulfilled by
  132. // dynamically generated client-specific entries in transient Spec
  133. // files.
  134. //
  135. // CDI can collect Spec files from multiple directories. Spec files are
  136. // automatically assigned priorities according to which directory they
  137. // were loaded from. The later a directory occurs in the list of CDI
  138. // directories to scan, the higher priority Spec files loaded from that
  139. // directory are assigned to. When two or more Spec files define the
  140. // same device, conflict is resolved by choosing the definition from the
  141. // Spec file with the highest priority.
  142. //
  143. // The default CDI directory configuration is chosen to encourage
  144. // separating dynamically generated CDI Spec files from static ones.
  145. // The default directories are '/etc/cdi' and '/var/run/cdi'. By putting
  146. // dynamically generated Spec files under '/var/run/cdi', those take
  147. // precedence over static ones in '/etc/cdi'. With this scheme, static
  148. // Spec files, typically installed by distro-specific packages, go into
  149. // '/etc/cdi' while all the dynamically generated Spec files, transient
  150. // or other, go into '/var/run/cdi'.
  151. //
  152. // Spec File Generation
  153. //
  154. // CDI offers two functions for writing and removing dynamically generated
  155. // Specs from CDI Spec directories. These functions, WriteSpec() and
  156. // RemoveSpec() implicitly follow the principle of separating dynamic Specs
  157. // from the rest and therefore always write to and remove Specs from the
  158. // last configured directory.
  159. //
  160. // Corresponding functions are also provided for generating names for Spec
  161. // files. These functions follow a simple naming convention to ensure that
  162. // multiple entities generating Spec files simultaneously on the same host
  163. // do not end up using conflicting Spec file names. GenerateSpecName(),
  164. // GenerateNameForSpec(), GenerateTransientSpecName(), and
  165. // GenerateTransientNameForSpec() all generate names which can be passed
  166. // as such to WriteSpec() and subsequently to RemoveSpec().
  167. //
  168. // Generating a Spec file for a vendor/device class can be done with a
  169. // code snippet similar to the following:
  170. //
  171. // import (
  172. // "fmt"
  173. // ...
  174. // "github.com/container-orchestrated-devices/container-device-interface/specs-go"
  175. // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
  176. // )
  177. //
  178. // func generateDeviceSpecs() error {
  179. // registry := cdi.GetRegistry()
  180. // spec := &specs.Spec{
  181. // Version: specs.CurrentVersion,
  182. // Kind: vendor+"/"+class,
  183. // }
  184. //
  185. // for _, dev := range enumerateDevices() {
  186. // spec.Devices = append(spec.Devices, specs.Device{
  187. // Name: dev.Name,
  188. // ContainerEdits: getContainerEditsForDevice(dev),
  189. // })
  190. // }
  191. //
  192. // specName, err := cdi.GenerateNameForSpec(spec)
  193. // if err != nil {
  194. // return fmt.Errorf("failed to generate Spec name: %w", err)
  195. // }
  196. //
  197. // return registry.SpecDB().WriteSpec(spec, specName)
  198. // }
  199. //
  200. // Similarly, generating and later cleaning up transient Spec files can be
  201. // done with code fragments similar to the following. These transient Spec
  202. // files are temporary Spec files with container-specific parametrization.
  203. // They are typically created before the associated container is created
  204. // and removed once that container is removed.
  205. //
  206. // import (
  207. // "fmt"
  208. // ...
  209. // "github.com/container-orchestrated-devices/container-device-interface/specs-go"
  210. // "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
  211. // )
  212. //
  213. // func generateTransientSpec(ctr Container) error {
  214. // registry := cdi.GetRegistry()
  215. // devices := getContainerDevs(ctr, vendor, class)
  216. // spec := &specs.Spec{
  217. // Version: specs.CurrentVersion,
  218. // Kind: vendor+"/"+class,
  219. // }
  220. //
  221. // for _, dev := range devices {
  222. // spec.Devices = append(spec.Devices, specs.Device{
  223. // // the generated name needs to be unique within the
  224. // // vendor/class domain on the host/node.
  225. // Name: generateUniqueDevName(dev, ctr),
  226. // ContainerEdits: getEditsForContainer(dev),
  227. // })
  228. // }
  229. //
  230. // // transientID is expected to guarantee that the Spec file name
  231. // // generated using <vendor, class, transientID> is unique within
  232. // // the host/node. If more than one device is allocated with the
  233. // // same vendor/class domain, either all generated Spec entries
  234. // // should go to a single Spec file (like in this sample snippet),
  235. // // or transientID should be unique for each generated Spec file.
  236. // transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
  237. // specName, err := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
  238. // if err != nil {
  239. // return fmt.Errorf("failed to generate Spec name: %w", err)
  240. // }
  241. //
  242. // return registry.SpecDB().WriteSpec(spec, specName)
  243. // }
  244. //
  245. // func removeTransientSpec(ctr Container) error {
  246. // registry := cdi.GetRegistry()
  247. // transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
  248. // specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
  249. //
  250. // return registry.SpecDB().RemoveSpec(specName)
  251. // }
  252. //
  253. // CDI Spec Validation
  254. //
  255. // This package performs both syntactic and semantic validation of CDI
  256. // Spec file data when a Spec file is loaded via the registry or using
  257. // the ReadSpec API function. As part of the semantic verification, the
  258. // Spec file is verified against the CDI Spec JSON validation schema.
  259. //
  260. // If a valid externally provided JSON validation schema is found in
  261. // the filesystem at /etc/cdi/schema/schema.json it is loaded and used
  262. // as the default validation schema. If such a file is not found or
  263. // fails to load, an embedded no-op schema is used.
  264. //
  265. // The used validation schema can also be changed programmatically using
  266. // the SetSchema API convenience function. This function also accepts
  267. // the special "builtin" (BuiltinSchemaName) and "none" (NoneSchemaName)
  268. // schema names which switch the used schema to the in-repo validation
  269. // schema embedded into the binary or the now default no-op schema
  270. // correspondingly. Other names are interpreted as the path to the actual
  271. // validation schema to load and use.
  272. package cdi