Skip to content

Commit 8e515b1

Browse files
committed
pkg/cdi: add K8s annotation handling functions.
Add functions for annotating containers for CDI device injection and for extracting CDI device names from such annotations. These functions can be used by CDI-aware devices plugins and runtimes, to request the injection of CDI devices and to recover the names of devices to inject correspondingly. For example, the following call to AnnotateInjection annotations := map[string]string{} AnnotateInjection(annotations, "vendor.gpu", []string{ "vendor.com/gpu=gpu0", "vendor.com/gpu=gpu1", }) produces the following annotations map[string]string{ "cdi.k8s.io/vendor.gpu": "vendor.com/gpu=gpu0,vendor.com/gpu=gpu1", } Subsequently parsing these annotations with devices, err := ParseAnnotations(annotations) extracts and returns the original slice of devices to inject: []string{ "vendor.com/gpu=gpu0", "vendor.com/gpu=gpu1", } Notes: These functions provide a temporary solution for delivering CDI device injection requests from either a client or a K8s Device Plugin to the container runtime. They can be removed once dedicated fields to the same effect are added natively to the CRI protocol. Signed-off-by: Krisztian Litkey <[email protected]>
1 parent d59fc65 commit 8e515b1

File tree

2 files changed

+597
-0
lines changed

2 files changed

+597
-0
lines changed

pkg/cdi/annotations.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Copyright © 2021-2022 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 cdi
18+
19+
import (
20+
"strings"
21+
22+
"github.com/pkg/errors"
23+
)
24+
25+
const (
26+
// AnnotationPrefix is the prefix for CDI container annotation keys.
27+
AnnotationPrefix = "cdi.k8s.io/"
28+
)
29+
30+
// UpdateAnnotations updates annotations with a plugin-specific CDI device
31+
// injection request for the given devices. Upon any error a non-nil error
32+
// is returned and annotations are left intact. By convention plugin should
33+
// be in the format of "vendor.device-type".
34+
func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
35+
key, err := AnnotationKey(plugin, deviceID)
36+
if err != nil {
37+
return annotations, errors.Wrap(err, "CDI annotation failed")
38+
}
39+
if _, ok := annotations[key]; ok {
40+
return annotations, errors.Errorf("CDI annotation failed, key %q used", key)
41+
}
42+
value, err := AnnotationValue(devices)
43+
if err != nil {
44+
return annotations, errors.Wrap(err, "CDI annotation failed")
45+
}
46+
47+
if annotations == nil {
48+
annotations = make(map[string]string)
49+
}
50+
annotations[key] = value
51+
52+
return annotations, nil
53+
}
54+
55+
// ParseAnnotations parses annotations for CDI device injection requests.
56+
// The keys and devices from all such requests are collected into slices
57+
// which are returned as the result. All devices are expected to be fully
58+
// qualified CDI device names. If any device fails this check empty slices
59+
// are returned along with a non-nil error. The annotations are expected
60+
// to be formatted by, or in a compatible fashion to UpdateAnnotations().
61+
func ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
62+
var (
63+
keys []string
64+
devices []string
65+
)
66+
67+
for key, value := range annotations {
68+
if !strings.HasPrefix(key, AnnotationPrefix) {
69+
continue
70+
}
71+
for _, d := range strings.Split(value, ",") {
72+
if !IsQualifiedName(d) {
73+
return nil, nil, errors.Errorf("invalid CDI device name %q", d)
74+
}
75+
devices = append(devices, d)
76+
}
77+
keys = append(keys, key)
78+
}
79+
80+
return keys, devices, nil
81+
}
82+
83+
// AnnotationKey returns a unique annotation key for an device allocation
84+
// by a K8s device plugin. pluginName should be in the format of
85+
// "vendor.device-type". deviceID is the ID of the device the plugin is
86+
// allocating. It is used to make sure that the generated key is unique
87+
// even if multiple allocations by a single plugin needs to be annotated.
88+
func AnnotationKey(pluginName, deviceID string) (string, error) {
89+
const maxNameLen = 63
90+
91+
if pluginName == "" {
92+
return "", errors.New("invalid plugin name, empty")
93+
}
94+
if deviceID == "" {
95+
return "", errors.New("invalid deviceID, empty")
96+
}
97+
98+
name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
99+
100+
if len(name) > maxNameLen {
101+
return "", errors.Errorf("invalid plugin+deviceID %q, too long", name)
102+
}
103+
104+
if c := rune(name[0]); !isAlphaNumeric(c) {
105+
return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric",
106+
name, c)
107+
}
108+
if len(name) > 2 {
109+
for _, c := range name[1 : len(name)-1] {
110+
switch {
111+
case isAlphaNumeric(c):
112+
case c == '_' || c == '-' || c == '.':
113+
default:
114+
return "", errors.Errorf("invalid name %q, invalid charcter '%c'",
115+
name, c)
116+
}
117+
}
118+
}
119+
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
120+
return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric",
121+
name, c)
122+
}
123+
124+
return AnnotationPrefix + name, nil
125+
}
126+
127+
// AnnotationValue returns an annotation value for the given devices.
128+
func AnnotationValue(devices []string) (string, error) {
129+
value, sep := "", ""
130+
for _, d := range devices {
131+
if _, _, _, err := ParseQualifiedName(d); err != nil {
132+
return "", err
133+
}
134+
value += sep + d
135+
sep = ","
136+
}
137+
138+
return value, nil
139+
}

0 commit comments

Comments
 (0)