2022-02-28 12:32:27 +00:00
|
|
|
/*
|
|
|
|
Copyright © 2021-2022 The CDI Authors
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cdi
|
|
|
|
|
|
|
|
import (
|
2023-01-19 10:44:02 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-02-28 12:32:27 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// AnnotationPrefix is the prefix for CDI container annotation keys.
|
|
|
|
AnnotationPrefix = "cdi.k8s.io/"
|
|
|
|
)
|
|
|
|
|
|
|
|
// UpdateAnnotations updates annotations with a plugin-specific CDI device
|
|
|
|
// injection request for the given devices. Upon any error a non-nil error
|
|
|
|
// is returned and annotations are left intact. By convention plugin should
|
|
|
|
// be in the format of "vendor.device-type".
|
|
|
|
func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
|
|
|
|
key, err := AnnotationKey(plugin, deviceID)
|
|
|
|
if err != nil {
|
2023-01-19 10:44:02 +00:00
|
|
|
return annotations, fmt.Errorf("CDI annotation failed: %w", err)
|
2022-02-28 12:32:27 +00:00
|
|
|
}
|
|
|
|
if _, ok := annotations[key]; ok {
|
2023-01-19 10:44:02 +00:00
|
|
|
return annotations, fmt.Errorf("CDI annotation failed, key %q used", key)
|
2022-02-28 12:32:27 +00:00
|
|
|
}
|
|
|
|
value, err := AnnotationValue(devices)
|
|
|
|
if err != nil {
|
2023-01-19 10:44:02 +00:00
|
|
|
return annotations, fmt.Errorf("CDI annotation failed: %w", err)
|
2022-02-28 12:32:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if annotations == nil {
|
|
|
|
annotations = make(map[string]string)
|
|
|
|
}
|
|
|
|
annotations[key] = value
|
|
|
|
|
|
|
|
return annotations, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseAnnotations parses annotations for CDI device injection requests.
|
|
|
|
// The keys and devices from all such requests are collected into slices
|
|
|
|
// which are returned as the result. All devices are expected to be fully
|
|
|
|
// qualified CDI device names. If any device fails this check empty slices
|
|
|
|
// are returned along with a non-nil error. The annotations are expected
|
|
|
|
// to be formatted by, or in a compatible fashion to UpdateAnnotations().
|
|
|
|
func ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
|
|
|
|
var (
|
|
|
|
keys []string
|
|
|
|
devices []string
|
|
|
|
)
|
|
|
|
|
|
|
|
for key, value := range annotations {
|
|
|
|
if !strings.HasPrefix(key, AnnotationPrefix) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for _, d := range strings.Split(value, ",") {
|
|
|
|
if !IsQualifiedName(d) {
|
2023-01-19 10:44:02 +00:00
|
|
|
return nil, nil, fmt.Errorf("invalid CDI device name %q", d)
|
2022-02-28 12:32:27 +00:00
|
|
|
}
|
|
|
|
devices = append(devices, d)
|
|
|
|
}
|
|
|
|
keys = append(keys, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
return keys, devices, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnnotationKey returns a unique annotation key for an device allocation
|
|
|
|
// by a K8s device plugin. pluginName should be in the format of
|
|
|
|
// "vendor.device-type". deviceID is the ID of the device the plugin is
|
|
|
|
// allocating. It is used to make sure that the generated key is unique
|
|
|
|
// even if multiple allocations by a single plugin needs to be annotated.
|
|
|
|
func AnnotationKey(pluginName, deviceID string) (string, error) {
|
|
|
|
const maxNameLen = 63
|
|
|
|
|
|
|
|
if pluginName == "" {
|
|
|
|
return "", errors.New("invalid plugin name, empty")
|
|
|
|
}
|
|
|
|
if deviceID == "" {
|
|
|
|
return "", errors.New("invalid deviceID, empty")
|
|
|
|
}
|
|
|
|
|
|
|
|
name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
|
|
|
|
|
|
|
|
if len(name) > maxNameLen {
|
2023-01-19 10:44:02 +00:00
|
|
|
return "", fmt.Errorf("invalid plugin+deviceID %q, too long", name)
|
2022-02-28 12:32:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if c := rune(name[0]); !isAlphaNumeric(c) {
|
2023-01-19 10:44:02 +00:00
|
|
|
return "", fmt.Errorf("invalid name %q, first '%c' should be alphanumeric",
|
2022-02-28 12:32:27 +00:00
|
|
|
name, c)
|
|
|
|
}
|
|
|
|
if len(name) > 2 {
|
|
|
|
for _, c := range name[1 : len(name)-1] {
|
|
|
|
switch {
|
|
|
|
case isAlphaNumeric(c):
|
|
|
|
case c == '_' || c == '-' || c == '.':
|
|
|
|
default:
|
2023-01-19 10:44:02 +00:00
|
|
|
return "", fmt.Errorf("invalid name %q, invalid charcter '%c'",
|
2022-02-28 12:32:27 +00:00
|
|
|
name, c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
|
2023-01-19 10:44:02 +00:00
|
|
|
return "", fmt.Errorf("invalid name %q, last '%c' should be alphanumeric",
|
2022-02-28 12:32:27 +00:00
|
|
|
name, c)
|
|
|
|
}
|
|
|
|
|
|
|
|
return AnnotationPrefix + name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnnotationValue returns an annotation value for the given devices.
|
|
|
|
func AnnotationValue(devices []string) (string, error) {
|
|
|
|
value, sep := "", ""
|
|
|
|
for _, d := range devices {
|
|
|
|
if _, _, _, err := ParseQualifiedName(d); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
value += sep + d
|
|
|
|
sep = ","
|
|
|
|
}
|
|
|
|
|
|
|
|
return value, nil
|
|
|
|
}
|