Skip to content

Commit a2f3354

Browse files
committed
cmd/cdi: add sample binary to exercise the API.
Add a sample 'cdi' binary to exercise the API. It can also serve as a CDI command line debug utility for Spec maintainers. Signed-off-by: Krisztian Litkey <[email protected]>
1 parent 46744e2 commit a2f3354

File tree

13 files changed

+1127
-0
lines changed

13 files changed

+1127
-0
lines changed

cmd/cdi/cmd/cdi-api.go

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/*
2+
Copyright © 2021 The CDI Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"fmt"
21+
"path/filepath"
22+
"sort"
23+
"strings"
24+
25+
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
26+
oci "github.com/opencontainers/runtime-spec/specs-go"
27+
gen "github.com/opencontainers/runtime-tools/generate"
28+
"github.com/pkg/errors"
29+
)
30+
31+
func cdiListVendors() {
32+
var (
33+
registry = cdi.GetRegistry()
34+
vendors = registry.SpecDB().ListVendors()
35+
)
36+
37+
if len(vendors) == 0 {
38+
fmt.Printf("No CDI vendors found.\n")
39+
return
40+
}
41+
42+
fmt.Printf("CDI vendors found:\n")
43+
for idx, vendor := range vendors {
44+
fmt.Printf(" %d. %q (%d CDI Spec Files)\n", idx, vendor,
45+
len(registry.SpecDB().GetVendorSpecs(vendor)))
46+
}
47+
}
48+
49+
func cdiListClasses() {
50+
var (
51+
registry = cdi.GetRegistry()
52+
vendors = map[string][]string{}
53+
)
54+
55+
for _, class := range registry.SpecDB().ListClasses() {
56+
vendors[class] = []string{}
57+
for _, vendor := range registry.SpecDB().ListVendors() {
58+
for _, spec := range registry.SpecDB().GetVendorSpecs(vendor) {
59+
if spec.GetClass() == class {
60+
vendors[class] = append(vendors[class], vendor)
61+
}
62+
}
63+
}
64+
}
65+
66+
if len(vendors) == 0 {
67+
fmt.Printf("No CDI device classes found.\n")
68+
return
69+
}
70+
71+
fmt.Printf("CDI device classes found:\n")
72+
for idx, class := range registry.SpecDB().ListClasses() {
73+
sort.Strings(vendors[class])
74+
fmt.Printf(" %d. %s (%d vendors: %s)\n", idx, class,
75+
len(vendors[class]), strings.Join(vendors[class], ", "))
76+
}
77+
}
78+
79+
func cdiListDevices(verbose bool, format string) {
80+
var (
81+
registry = cdi.GetRegistry()
82+
devices = registry.DeviceDB().ListDevices()
83+
)
84+
85+
if len(devices) == 0 {
86+
fmt.Printf("No CDI devices found.\n")
87+
return
88+
}
89+
90+
fmt.Printf("CDI devices found:\n")
91+
for idx, device := range devices {
92+
cdiPrintDevice(idx, registry.DeviceDB().GetDevice(device), verbose, format, 2)
93+
}
94+
}
95+
96+
func cdiPrintDevice(idx int, dev *cdi.Device, verbose bool, format string, level int) {
97+
if !verbose {
98+
if idx >= 0 {
99+
fmt.Printf("%s%d. %s\n", indent(level), idx, dev.GetQualifiedName())
100+
return
101+
}
102+
fmt.Printf("%s%s\n", indent(level), dev.GetQualifiedName())
103+
return
104+
}
105+
106+
var (
107+
spec = dev.GetSpec()
108+
)
109+
110+
format = chooseFormat(format, spec.GetPath())
111+
112+
fmt.Printf(" %s (%s)\n", dev.GetQualifiedName(), spec.GetPath())
113+
fmt.Printf("%s", marshalObject(level+2, dev.Device, format))
114+
edits := spec.ContainerEdits
115+
if len(edits.Env)+len(edits.DeviceNodes)+len(edits.Hooks)+len(edits.Mounts) > 0 {
116+
fmt.Printf("%s global Spec containerEdits:\n", indent(level+2))
117+
fmt.Printf("%s", marshalObject(level+4, spec.ContainerEdits, format))
118+
}
119+
}
120+
121+
func cdiShowSpecDirs() {
122+
var (
123+
registry = cdi.GetRegistry()
124+
specDirs = registry.GetSpecDirectories()
125+
cdiErrors = registry.GetErrors()
126+
)
127+
fmt.Printf("CDI Spec directories in use:\n")
128+
for prio, dir := range specDirs {
129+
fmt.Printf(" %s (priority %d)\n", dir, prio)
130+
for path, specErrors := range cdiErrors {
131+
if filepath.Dir(path) != dir {
132+
continue
133+
}
134+
for _, err := range specErrors {
135+
fmt.Printf(" - has error %v\n", err)
136+
}
137+
}
138+
}
139+
}
140+
141+
func cdiInjectDevices(format string, ociSpec *oci.Spec, patterns []string) error {
142+
var (
143+
registry = cdi.GetRegistry()
144+
matches = map[string]struct{}{}
145+
devices = []string{}
146+
)
147+
148+
for _, device := range registry.DeviceDB().ListDevices() {
149+
for _, glob := range patterns {
150+
match, err := filepath.Match(glob, device)
151+
if err != nil {
152+
return errors.Wrapf(err, "failed to match pattern %q against %q",
153+
glob, device)
154+
}
155+
if match {
156+
matches[device] = struct{}{}
157+
}
158+
}
159+
}
160+
for device := range matches {
161+
devices = append(devices, device)
162+
}
163+
sort.Strings(devices)
164+
165+
unresolved, err := registry.InjectDevices(ociSpec, devices...)
166+
167+
if len(unresolved) > 0 {
168+
fmt.Printf("Unresolved CDI devices:\n")
169+
for idx, device := range unresolved {
170+
fmt.Printf(" %d. %s\n", idx, device)
171+
}
172+
}
173+
if err != nil {
174+
return errors.Wrapf(err, "OCI device injection failed")
175+
}
176+
177+
fmt.Printf("Updated OCI Spec:\n")
178+
fmt.Printf("%s", marshalObject(2, ociSpec, format))
179+
180+
return nil
181+
}
182+
183+
func cdiResolveDevices(ociSpecFiles ...string) error {
184+
var (
185+
cache *cdi.Cache
186+
ociSpec *oci.Spec
187+
devices []string
188+
unresolved []string
189+
err error
190+
)
191+
192+
if cache, err = cdi.NewCache(); err != nil {
193+
return errors.Wrap(err, "failed to create CDI cache instance")
194+
}
195+
196+
for _, ociSpecFile := range ociSpecFiles {
197+
ociSpec, err = readOCISpec(ociSpecFile)
198+
if err != nil {
199+
return err
200+
}
201+
202+
devices = collectCDIDevicesFromOCISpec(ociSpec)
203+
204+
unresolved, err = cache.InjectDevices(ociSpec, devices...)
205+
if len(unresolved) > 0 {
206+
fmt.Printf("Unresolved CDI devices:\n")
207+
for idx, device := range unresolved {
208+
fmt.Printf(" %d. %s\n", idx, device)
209+
}
210+
}
211+
if err != nil {
212+
return errors.Wrapf(err, "failed to resolve devices for OCI Spec %q", ociSpecFile)
213+
}
214+
215+
format := chooseFormat(injectCfg.output, ociSpecFile)
216+
fmt.Printf("%s", marshalObject(2, ociSpec, format))
217+
}
218+
219+
return nil
220+
}
221+
222+
func collectCDIDevicesFromOCISpec(spec *oci.Spec) []string {
223+
var (
224+
cdiDevs []string
225+
)
226+
227+
if spec.Linux == nil || len(spec.Linux.Devices) == 0 {
228+
return nil
229+
}
230+
231+
devices := spec.Linux.Devices
232+
g := gen.NewFromSpec(spec)
233+
g.ClearLinuxDevices()
234+
235+
for _, d := range devices {
236+
if !cdi.IsQualifiedName(d.Path) {
237+
g.AddDevice(d)
238+
continue
239+
}
240+
cdiDevs = append(cdiDevs, d.Path)
241+
}
242+
243+
return cdiDevs
244+
}
245+
246+
func cdiListSpecs(verbose bool, format string, vendors ...string) {
247+
var (
248+
registry = cdi.GetRegistry()
249+
)
250+
251+
format = chooseFormat(format, "format-as.yaml")
252+
253+
if len(vendors) == 0 {
254+
vendors = registry.SpecDB().ListVendors()
255+
}
256+
257+
if len(vendors) == 0 {
258+
fmt.Printf("No CDI Specs found.\n")
259+
cdiErrors := registry.GetErrors()
260+
if len(cdiErrors) > 0 {
261+
for path, specErrors := range cdiErrors {
262+
fmt.Printf("%s has errors:\n", path)
263+
for idx, err := range specErrors {
264+
fmt.Printf(" %d. %v\n", idx, err)
265+
}
266+
}
267+
}
268+
return
269+
}
270+
271+
fmt.Printf("CDI Specs found:\n")
272+
for _, vendor := range registry.SpecDB().ListVendors() {
273+
fmt.Printf("Vendor %s:\n", vendor)
274+
for _, spec := range registry.SpecDB().GetVendorSpecs(vendor) {
275+
cdiPrintSpec(spec, verbose, format, 2)
276+
cdiPrintSpecErrors(spec, verbose, 2)
277+
}
278+
}
279+
}
280+
281+
func cdiPrintSpec(spec *cdi.Spec, verbose bool, format string, level int) {
282+
fmt.Printf("%sSpec File %s\n", indent(level), spec.GetPath())
283+
284+
if verbose {
285+
fmt.Printf("%s", marshalObject(level+2, spec.Spec, format))
286+
}
287+
}
288+
289+
func cdiPrintSpecErrors(spec *cdi.Spec, verbose bool, level int) {
290+
var (
291+
registry = cdi.GetRegistry()
292+
cdiErrors = registry.GetErrors()
293+
)
294+
295+
if len(cdiErrors) > 0 {
296+
for path, specErrors := range cdiErrors {
297+
if len(specErrors) == 0 {
298+
continue
299+
}
300+
fmt.Printf("%s%s has %d errors:\n", indent(level), path, len(specErrors))
301+
for idx, err := range specErrors {
302+
fmt.Printf("%s%d. %v\n", indent(level+2), idx, err)
303+
}
304+
}
305+
}
306+
}
307+
308+
func cdiPrintRegistry(args ...string) {
309+
if len(args) == 0 {
310+
args = []string{"all"}
311+
}
312+
313+
for _, what := range args {
314+
switch what {
315+
case "vendors", "vendor":
316+
cdiListVendors()
317+
case "classes", "class":
318+
cdiListClasses()
319+
case "specs", "spec":
320+
cdiListSpecs(monitorCfg.verbose, monitorCfg.output)
321+
case "devices", "device":
322+
cdiListDevices(monitorCfg.verbose, monitorCfg.output)
323+
case "all":
324+
cdiListVendors()
325+
cdiListClasses()
326+
cdiListSpecs(monitorCfg.verbose, monitorCfg.output)
327+
cdiListDevices(monitorCfg.verbose, monitorCfg.output)
328+
default:
329+
fmt.Printf("Unrecognized CDI aspect/object %q... ignoring it\n", what)
330+
}
331+
}
332+
}
333+
334+
func cdiPrintRegistryErrors() {
335+
var (
336+
registry = cdi.GetRegistry()
337+
cdiErrors = registry.GetErrors()
338+
)
339+
340+
if len(cdiErrors) == 0 {
341+
return
342+
}
343+
344+
fmt.Printf("CDI Registry has errors:\n")
345+
for path, specErrors := range cdiErrors {
346+
fmt.Printf("Spec file %s:\n", path)
347+
for idx, err := range specErrors {
348+
fmt.Printf(" %d: %v", idx, err)
349+
}
350+
}
351+
}

cmd/cdi/cmd/classes.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
Copyright © 2021 The CDI Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"github.com/spf13/cobra"
21+
)
22+
23+
// classesCmd is our command for listing device classes in the registry.
24+
var classesCmd = &cobra.Command{
25+
Use: "classes",
26+
Short: "List CDI device classes",
27+
Long: `List CDI device classes found in the registry.`,
28+
Run: func(cmd *cobra.Command, args []string) {
29+
cdiListClasses()
30+
},
31+
}
32+
33+
func init() {
34+
rootCmd.AddCommand(classesCmd)
35+
}

0 commit comments

Comments
 (0)